#!/bin/bash
#
#  Dynamic Kernel Module Support (DKMS) <dkms-devel@dell.com>
#  Copyright (C) 2003-2008 Dell, Inc.
#  by Gary Lerhaupt, Matt Domsch, & Mario Limonciello
#  Copyright (C) 2012 by Darik Horn <dajhorn@vanadac.com>
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#

# Exit status values and error messages used by dkms:
# (as parameters to die, diewarn, report_build_problem, exit)
#
# 0: global: SUCCESS
# 0: remove_module(): Remove cancelled because --rpm_safe_upgrade scenario detected.
# 1: ldtarball: You must be root to use this command with the --force option.
# 1: check_root(): You must be root to use this command.
# 1: check_rw_dkms_tree(): No write access to DKMS tree at ...
# 1: mktemp_or_die(): Unable to make temporary file/directory.
# 1: check_module_args(): Arguments <module> and <module-version> are not specified.
# 1: run_match(): Invalid number of parameters passed.
# 1: global: If more than one arch is specified on the command line, then there "must be an equal number of kernel versions also specified (1:1 relationship).
# 2: global: Unknown option: ...
# 2: global: You cannot specify a kernel version and also specify --all on the command line.
# 2: run_match(): The templatekernel and the specified kernel version are the same.
# 2: add_module(): Could not find module source directory.
# 2: load_tarball(): ... does not exist.
# 3: global: You cannot specify an arch and also specify --all on the command line.
# 3: load_tarball(): Tarball does not appear to be a correctly formed DKMS archive. No dkms.conf found within it.
# 3: run_match(): The module: $module is not located in the DKMS tree.
# 3: add_module(): You cannot add the same module/version combo more than once.
# 3: prepare_build(): This module/version has already been built on: ...
# 3: module_is_added_or_die(): The module/version combo: ... is not located in the DKMS tree.
# 4: global: Cannot specify more than one action.
# 4: distro_version(): System is missing os-release file.
# 4: read_conf(): Could not locate dkms.conf file.
# 4: have_one_kernel(): The action ... does not support multiple kernel version parameters on the command line.
# 4: module_is_broken_and_die(): ... is broken!
# 5: check_all_is_banned(): The action ... does not support the --all parameter.
# 5: prepare_build(): Patch ... as specified in dkms.conf contains '..' path component.
# 5: prepare_build(): Patch ... as specified in dkms.conf cannot be found in ...
# 5: install: This module/version combo is already installed for kernel ...
# 6: prepare_build(): Application of patch ... failed.
# 6: install: You cannot install a module onto a non-existant kernel.
# 6: install: Installation aborted.
# 6: install: Install Failed (depmod problems). Module rolled back to built state.
# 6: make_tarball(): Modules must already be in the built state before using mktarball.
# 6: make_tarball(): Failed to make tarball.
# 7: load_tarball(): No valid dkms.conf in dkms_source_tree or dkms_binaries_only.
# 7: actual_build(): Build of ... failed for: ...
# 8: load_tarball(): ... is already added!
# 8: global: You have specified both --binaries-only and --source-only.
# 8: read_conf_or_die(): Bad conf file.
# 8: prepare_build(): The directory $source_dir does not appear to have module source located within it.
# 9: add_source_tree(): ... must contain a dkms.conf file!
# 9: make_tarball(): Missing write permissions for ...
# 9: load_tarball(): Unable to install ... using rpm.
# 10: add_source_tree(): Malformed dkms.conf file. Cannot load source tree.
# 10: build: Bad return status for module build on kernel: ...
# 11: autoinstall: One or more modules failed to install during autoinstall.
# 12: setup_kernels_arches(): Could not determine architecture.
# 13: build: Aborting build of module ... for kernel ... due to missing BUILD_DEPENDS: ...
# 14: kernel_preinst/prerm: dkms kernel_preinst/prerm for kernel ... failed for module(s) ...
# 15: gloabl: a call to cd failed
# 16: setup_kernels_arches()/do_build()/do_install(): empty $kernelver or $arch
# 21: prepare_kernel(): Your kernel headers for kernel ... cannot be found ...
# 77: skipped due to BUILD_EXCLUSIVE
# 101: install: pre_install failed, aborting install.

shopt -s extglob

# All of the variables we will accept from dkms.conf.
# Does not include directives
# The last group of variables has been deprecated
readonly dkms_conf_variables="PACKAGE_NAME
   PACKAGE_VERSION POST_ADD POST_BUILD POST_INSTALL POST_REMOVE PRE_BUILD
   PRE_INSTALL BUILD_DEPENDS BUILD_EXCLUSIVE_ARCH BUILD_EXCLUSIVE_CONFIG
   BUILD_EXCLUSIVE_KERNEL BUILD_EXCLUSIVE_KERNEL_MIN BUILD_EXCLUSIVE_KERNEL_MAX
   build_exclude OBSOLETE_BY MAKE MAKE_MATCH
   PATCH PATCH_MATCH patch_array BUILT_MODULE_NAME
   built_module_name BUILT_MODULE_LOCATION built_module_location
   DEST_MODULE_NAME dest_module_name
   DEST_MODULE_LOCATION dest_module_location
   STRIP strip AUTOINSTALL NO_WEAK_MODULES BUILD_DEPENDS_REBUILD

   CLEAN
   REMAKE_INITRD MODULES_CONF MODULES_CONF_OBSOLETES
   MODULES_CONF_ALIAS_TYPE MODULES_CONF_OBSOLETE_ONLY"
show_deprecation_warnings=0

# All of the variables not related to signing we will accept from framework.conf.
readonly dkms_framework_nonsigning_variables="source_tree dkms_tree install_tree tmp_location
   verbose symlink_modules autoinstall_all_kernels modprobe_on_install parallel_jobs
   compress_gzip_opts compress_xz_opts compress_zstd_opts build_environment post_transaction"
# All of the signing related variables we will accept from framework.conf.
readonly dkms_framework_signing_variables="sign_file mok_signing_key mok_certificate"

# The post transaction command we will accept from framework.conf
readonly dkms_framework_post_transaction="post_transaction"

# Some important regular expressions. Requires bash 3 or above.
# Any poor souls still running bash 2 or older really need an upgrade.
readonly mv_re='^([^/]*)/(.*)$'

# Areas that will vary between Linux and other OS's
_get_kernel_dir() {
    if [[ ! $ksourcedir_fromcli ]]; then
        KVER=$1
        DIR="$install_tree/$KVER/build"
        echo "$DIR"
    else
        echo "$kernel_source_dir"
    fi
}

_check_kernel_dir() {
    DIR=$(_get_kernel_dir "$1")
    [[ -e "$DIR/include" ]]
    return $?
}

on_exit()
{
    local exitcode_on_exit=$? # must be one line or $? captures rc from `local`
    local j
    j="$(jobs -p)"

    [[ $j ]] && kill "$j" 2>/dev/null

    [[ $make_tarball_rm_temp_dir_name ]] && eval "$make_tarball_rm_temp_dir_name"
    [[ $load_tarball_rm_temp_dir_name ]] && eval "$load_tarball_rm_temp_dir_name"

    exit $exitcode_on_exit
}

unset make_tarball_rm_temp_dir_name load_tarball_rm_temp_dir_name
trap on_exit EXIT

CP()
{
    cp --reflink=auto "$@"
}

# Run a command that we may or may not want to be detailed about.
invoke_command()
{
    # $1 = command to be executed using eval.
    # $2 = Description of command to run
    # $3 = Redirect command output (including stderr) to this file
    # $4 = background, if you want print . each 3 seconds while command runs
    local cmd
    local cmd_description
    local cmd_output_file
    local cmd_mode
    local exitval
    local progresspid
    cmd="$1"
    cmd_description="$2"
    cmd_output_file="$3"
    cmd_mode="$4"
    exitval=0

    [[ $verbose ]] && echo -e "$cmd" || echo -en "$cmd_description..."

    if [[ $cmd_mode == background && ! $verbose && $package_name != dkms*_test ]]; then
        while true ; do
            sleep 3
            echo -n "."
        done &
        progresspid=$!
    fi

    if [[ $cmd_output_file ]]; then
        local t_start
        local t_end
        # shellcheck disable=SC2129
        echo "" >> "$cmd_output_file"
        echo -e "$cmd_description" >> "$cmd_output_file"
        echo -e "# command: $cmd" >> "$cmd_output_file"
        t_start=$SECONDS
        ( eval "$cmd" ) >> "$cmd_output_file" 2>&1
        exitval=$?
        t_end=$SECONDS
        # shellcheck disable=SC2129
        echo -e "\n# exit code: $exitval" >> "$cmd_output_file"
        echo "# elapsed time: $(date -u -d "0 $t_end sec - $t_start sec" +%T)" >> "$cmd_output_file"
        echo "----------------------------------------------------------------" >> "$cmd_output_file"
    elif [[ ! $cmd_output_file && $cmd_mode = background && ! $verbose ]]; then
        ( eval "$cmd" ) >/dev/null 2>&1
        exitval=$?
    else
        ( eval "$cmd" )
        exitval=$?
    fi

    if [[ $progresspid ]]; then
        kill -9 "$progresspid" >/dev/null 2>&1
        wait "$progresspid" 2>/dev/null
    fi

    if (( exitval > 0 )); then
        echo -en "(bad exit status: $exitval)"
        # Print the failing command without the clunky redirection
        [[ ! $verbose ]] && echo -e "\nFailed command:\n$1"
    else
        echo " done."
    fi

    return "$exitval"
}

error() (
    exec >&2
    echo ""
    echo -n "Error! "
    for s in "$@"; do echo "$s"; done
)

warn() (
    exec >&2
    echo -n "Warning: "
    for s in "$@"; do echo "$s"; done
)

deprecated() (
    exec >&2
    echo -n "Deprecated feature: "
    for s in "$@"; do echo "$s"; done
)

# Print an error message and die with the passed error code.
die() {
    # $1 = error code to return with
    # rest = strings to print before we exit.
    ret=$1
    shift
    error "$@"
    [[ $die_is_fatal = yes ]] && exit "$ret" || return "$ret"
}

# Print a warning message and die with the passed error code.
diewarn() {
    # $1 = error code to return with
    # rest = strings to print before we exit.
    ret=$1
    shift
    warn "$@"
    [[ $die_is_fatal = yes ]] && exit "$ret" || return "$ret"
}

mktemp_or_die() {
    local t
    t=$(mktemp "$@") && echo "$t" && return
    [[ $* = *-d* ]] && die 1 "Unable to make temporary directory."
    die 1 "Unable to make temporary file."
}

show_usage()
{
    echo "Usage: $0 [action] [options]"
    echo "  [action]  = { add | remove | build | unbuild | install | uninstall | match |"
    echo "               autoinstall | mktarball | ldtarball | status | generate_mok |"
    echo "               kernel_preinst | kernel_postinst | kernel_prerm }"
    echo "  [options] = [-m module] [-v module-version] [-k kernel-version] [-a arch]"
    echo "              [-c dkms.conf-location] [-q] [--force] [--force-version-override] [--all]"
    echo "              [--templatekernel=kernel] [--directive='cli-directive=cli-value']"
    echo "              [--config=kernel-include/config/auto.conf-location] [--archive=tarball-location]"
    echo "              [--kernelsourcedir=source-location] [--rpm_safe_upgrade]"
    echo "              [--dkmstree path] [--sourcetree path] [--installtree path]"
    echo "              [--binaries-only] [--source-only] [--verbose]"
    echo "              [--no-depmod] [--modprobe-on-install] [-j number] [--version] [--help]"
}

VER()
{
    # $1 = kernel version string

    # Pad all numbers in $1 so that they have at least three digits, e.g.,
    #   2.6.9-1cvs200409091247 => 002.006.009-001cvs200409091247
    # The result should compare correctly as a string.

    echo "$1" | sed -e 's:\([^0-9]\)\([0-9]\):\1 \2:g' \
        -e 's:\([0-9]\)\([^0-9]\):\1 \2:g' \
        -e 's:\(.*\): \1 :' \
        -e 's: \([0-9]\) : 00\1 :g' \
        -e 's: \([0-9][0-9]\) : 0\1 :g' \
        -e 's: ::g'
}

# Find out how many CPUs there are so that we may pass an appropriate -j
# option to make. Ignore hyperthreading for now.
get_num_cpus()
{
    # use nproc(1) from coreutils 8.1-1+ if available, otherwise single job
    if [[ -x /usr/bin/nproc ]]; then
        nproc
    else
        echo "1"
    fi
}

# Finds a .ko or .ko.xz based on a directory and module name
# must call set_module_suffix first
compressed_or_uncompressed()
{
    # module dir = $1
    # module = $2
    local test1
    local test2
    test1="$1/$2$module_uncompressed_suffix"
    test2="$1/$2$module_uncompressed_suffix$module_compressed_suffix"
    if [[ -e "$test1" ]]; then
        echo "$test1"
    elif [[ -e "$test2" ]]; then
        echo "$test2"
    fi
}

# Finds .ko or .ko.xz based on a tree and module name
# must call set_module_suffix first
find_module()
{
    # tree = $1
    # module = $2
    find "$1" -name "$2$module_uncompressed_suffix" -type f -o -name "$2$module_suffix" -type f
    return $?
}

# Figure out the correct module suffix for the kernel we are currently dealing
# with, which may or may not be the currently installed kernel. Do not use modules.dep
# as it might not be available (in Red Hat removal is triggered with kernel-core but modules.dep
# is contained in kernel-modules-core).
set_module_suffix()
{
    # $1 = the kernel to base the module_suffix on
    kernel_test="${1:-$(uname -r)}"
    module_uncompressed_suffix=".ko"
    find "$install_tree/$kernel_test/" -name "*.ko.gz" 2>/dev/null | grep -q . && module_compressed_suffix=".gz"
    find "$install_tree/$kernel_test/" -name "*.ko.xz" 2>/dev/null | grep -q . && module_compressed_suffix=".xz"
    find "$install_tree/$kernel_test/" -name "*.ko.zst" 2>/dev/null | grep -q . && module_compressed_suffix=".zst"
    module_suffix="$module_uncompressed_suffix$module_compressed_suffix"
}

set_kernel_source_dir_and_kconfig()
{
    if [[ ! $ksourcedir_fromcli ]]; then
        # $1 = the kernel to base the directory on
        kernel_source_dir="$(_get_kernel_dir "$1")"
    fi
    if [[ ! $kconfig_fromcli ]]; then
        if [[ -e "${kernel_source_dir}/include/config/auto.conf" ]]; then
            kernel_config="${kernel_source_dir}/include/config/auto.conf"
        else
            kernel_config="${kernel_source_dir}/.config"
        fi
    fi
}

check_all_is_banned()
{
    if [[ $all ]]; then
        die 5 "The action $1 does not support the --all parameter."
    fi
}

# A little test function for DKMS commands that only work on one kernel.
have_one_kernel() {
    check_all_is_banned "$1"
    if (( ${#kernelver[@]} != 1 )); then
        [[ $1 =~ kernel_(preinst|postinst|prerm) ]] && die 4 "The action $1 requires exactly one kernel version parameter on the command line."
        die 4 "The action $1 does not support multiple kernel version parameters on the command line."
    fi
}

# Set up the kernelver and arch arrays.  You must have a 1:1 correspondence --
# if there is an entry in kernelver[$i], there must also be an entry in arch[$i]
# Note the special casing for the status action -- the status functions just
# report on what we already have, and will break with the preprocessing that
# this function provides.
setup_kernels_arches()
{
    # If all is set, use dkms status to fill the arrays
    if [[ $all && $1 != status ]]; then
        local i
        i=0
        while read -r line; do
            [[ $line ]] || continue
            line=${line#*/}; line=${line#*/};
            # (I would leave out the delimiters in the status output
            #  in the first place.)
            kernelver[i]=${line%/*}
            arch[i]=${line#*/}
            i=$((i + 1))
        done <<< "$(module_status_built "$module" "$module_version" | sort -V)"
    fi

    # Set default kernel version and arch, if none set (but only --all isn't set)
    if [[ $1 != status ]]; then
        if [[ ${#kernelver[@]} -eq 0 && ! $all ]]; then
            kernelver[0]=$(uname -r)
        fi
        if [[ ${#arch[@]} -eq 0 ]]; then
            case "$running_distribution" in
                debian* | ubuntu* | arch* | gentoo*)
                    arch[0]=$(uname -m)
                    ;;
                *)
                    kernelver_rpm=$(rpm -qf "$install_tree/${kernelver[0]}" 2>/dev/null | \
                    grep -v "not owned by any package" | grep kernel | head -n 1)
                    if ! arch[0]=$(rpm -q --queryformat "%{ARCH}" "$kernelver_rpm" 2>/dev/null); then
                        arch[0]=$(uname -m)
                        # shellcheck disable=SC2010
                        # TODO: replace ls | grep
                        if [[ ${arch[0]} = x86_64 ]] && [[ -r /proc/cpuinfo ]] && grep -q Intel /proc/cpuinfo && ls "$install_tree/${kernelver[0]}/build/configs" 2>/dev/null | grep -q "ia32e"; then
                            arch[0]="ia32e"
                        fi
                    fi
                    ;;
            esac
        fi
        if [[ ${#arch[@]} -eq 0 ]]; then
            die 12 "Could not determine architecture."
        fi
    fi

    # If only one arch is specified, make it so for all the kernels
    if ((${#arch[@]} == 1 && ${#kernelver[@]} > 1)); then
        while ((${#arch[@]} < ${#kernelver[@]})); do
            arch[${#arch[@]}]=${arch[0]}
        done
    fi

    if [[ $1 != status ]]; then
        local i
        for ((i=0; i < ${#kernelver[@]}; i++)); do
            [[ ${kernelver[i]} ]] || die 16 "Empty kernelver[$i]"
        done
        for ((i=0; i < ${#arch[@]}; i++)); do
            [[ ${arch[i]} ]] || die 16 "Empty arch[$i]"
        done
    fi
}

do_depmod()
{
    if [[ $no_depmod ]]; then
        return
    fi
    # $1 = kernel version
    if [[ ${current_os} != Linux ]] ; then
        return
    fi
    if [[ ! -f $install_tree/$1/modules.dep ]]; then
        # if the corresponding linux image $1 is not installed
        # do not create modules.dep
        echo "Skipping depmod because '$install_tree/$1/modules.dep' is missing."
        return
    fi
    if [[ -f /boot/System.map-$1 ]]; then
        depmod -a "$1" -F "/boot/System.map-$1"
    else
        depmod -a "$1"
    fi
    if [[ -f $install_tree/$1/modules.dep && ! -s $install_tree/$1/modules.dep ]]; then
        # if modules.dep is empty, we just removed the last kernel module from
        # no longer installed kernel $1, so do not leave stale depmod files around
        rm -fv "${install_tree:?}/$1/"modules.{alias,dep,devname,softdep,symbols,weakdep,*.bin}
        rmdir --ignore-fail-on-non-empty "${install_tree:?}/$1"
        [[ -d $install_tree/$1 ]] || echo "removed directory $install_tree/$1"
    fi
}

# Grab distro information from os-release.
distro_version()
{
    for f in /etc/os-release /usr/lib/os-release; do
        if [[ -e $f ]]; then
            (
                # shellcheck disable=SC1090
                . "$f"
                if [[ "$ID" = "ubuntu" ]]; then
                    # ID_LIKE=debian in ubuntu
                    echo "$ID"
                elif [[ ${#ID_LIKE[@]} != 0 ]]; then
                    echo "${ID_LIKE[0]}"
                else
                    echo "$ID"
                fi
            )
            return
        fi
    done
    die 4 "System is missing os-release file."
}

override_dest_module_location()
{
    local orig_location
    orig_location="$1"
    [[ ${addon_modules_dir} ]] && echo "/${addon_modules_dir}" && return

    case "$running_distribution" in
    fedora* | rhel* | ovm*)
        echo "/extra" && return
        ;;
    sles* | suse* | opensuse*)
        echo "/updates" && return
        ;;
    debian* | ubuntu*)
        echo "/updates/dkms" && return
        ;;
    arch*)
        echo "/updates/dkms" && return
        ;;
    *)
        ;;
    esac
    echo "$orig_location"
}

# Source a file safely.
# We want to ensure that the .conf file we source does not stomp all over
# parts of the environment we don't want them to.  This makes it so that
# it is harder to accidentally corrupt our environment.  conf files can
# still deliberately trash the environment by abusing dkms_directive env
# variables or by crafting special values that will make eval do evil things.
safe_source() {
    # $1 = file to source
    # $@ = environment variables to echo out
    local to_source_file
    to_source_file="$1"; shift
    # shellcheck disable=SC2206
    # we intentionally split the string into a multi-element array
    declare -a -r export_envs=( $@ )
    # shellcheck disable=SC2034
    # false positive
    local -r CR=$(echo -e '\r')
    local tmpfile
    tmpfile=$(mktemp_or_die)
    ( exec >"$tmpfile"
    # shellcheck disable=SC1090
    . "$to_source_file" >/dev/null
    # This is really ugly, but a neat hack
    # Remember, in bash 2.0 and greater all variables are really arrays.
    for _export_env in "${export_envs[@]}"; do
        # shellcheck disable=SC1083,SC2294
        for _i in $(eval echo \${!"${_export_env}"[@]}); do
            eval echo '$_export_env[$_i]=\"${'"${_export_env}"'[$_i]%$CR}\"'
        done
    done

    # handle DKMS_DIRECTIVE stuff specially.
    for directive in $(set | grep ^DKMS_DIRECTIVE | cut -d = -f 2-3); do
        directive_name=${directive%%=*}
        directive_value=${directive#*=}
        echo "$directive_name=\"$directive_value\""
    done
    )
    # shellcheck disable=SC1090
    . "$tmpfile"
    rm "${tmpfile:?}"

    (( show_deprecation_warnings )) || return
    if (( ${#CLEAN[@]} )); then
        case "$CLEAN" in
            true|/bin/true|/usr/bin/true) ;;
            *)  deprecated "CLEAN ($to_source_file)" ;;
        esac
    fi
    (( ${#REMAKE_INITRD[@]} )) && deprecated "REMAKE_INITRD ($to_source_file)"
    (( ${#MODULES_CONF[@]} )) && deprecated "MODULES_CONF ($to_source_file)"
    (( ${#MODULES_CONF_OBSOLETES[@]} )) && deprecated "MODULES_CONF_OBSOLETES ($to_source_file)"
    (( ${#MODULES_CONF_ALIAS_TYPE[@]} )) && deprecated "MODULES_CONF_ALIAS_TYPE ($to_source_file)"
    (( ${#MODULES_CONF_OBSOLETE_ONLY[@]} )) && deprecated "MODULES_CONF_OBSOLETE_ONLY ($to_source_file)"
}

# Source a dkms.conf file and perform appropriate postprocessing on it.
# Do our best to not repeatedly source the same .conf file -- this can happen
# when chaining module installation functions or autoinstalling.
read_conf() {
    # $1 kernel version (required)
    # $2 arch (required)
    # $3 dkms.conf location (optional)

    local return_value
    local read_conf_file
    return_value=0
    read_conf_file="$dkms_tree/$module/$module_version/source/dkms.conf"

    # Set variables supported in dkms.conf files (eg. $kernelver)
    local kernelver
    local arch
    kernelver="$1"
    arch="$2"
    set_kernel_source_dir_and_kconfig "$1"


    # Find which conf file to check
    [[ $conf ]] && read_conf_file="$conf"
    [[ $3 ]] && read_conf_file="$3"

    [[ -r $read_conf_file ]] || die 4 "Could not locate dkms.conf file." \
    "File: $read_conf_file does not exist."

    [[ $last_mvka = $module/$module_version/$1/$2 && \
    $last_mvka_conf = $(readlink -f "$read_conf_file") ]] && return


    # Clear variables and arrays
    for var in $dkms_conf_variables; do
        unset "$var"
    done

    # Source in the dkms.conf.
    # Allow for user-specified overrides in order of specificity.
    local _conf_file
    for _conf_file in \
        "$read_conf_file" \
        "/etc/dkms/$module.conf" \
        "/etc/dkms/$module-$module_version.conf" \
        "/etc/dkms/$module-$module_version-$1.conf" \
        "/etc/dkms/$module-$module_version-$1-$2.conf" \
        "/etc/dkms/$module--$1.conf" \
        "/etc/dkms/$module--$1-$2.conf" \
    ; do
        [[ -e $_conf_file ]] && safe_source "$_conf_file" "$dkms_conf_variables"
    done

    # Source in the directive_array
    for directive in "${directive_array[@]}"; do
        directive_name=${directive%%=*}
        directive_value=${directive#*=}
        export "$directive_name"="$directive_value"
        echo "DIRECTIVE: $directive_name=\"$directive_value\""
    done

    # Set variables
    package_name="$PACKAGE_NAME"
    package_version="$PACKAGE_VERSION"
    # shellcheck disable=SC2153
    post_add="$POST_ADD"
    # shellcheck disable=SC2153
    post_build="$POST_BUILD"
    # shellcheck disable=SC2153
    post_install="$POST_INSTALL"
    # shellcheck disable=SC2153
    post_remove="$POST_REMOVE"
    # shellcheck disable=SC2153
    pre_build="$PRE_BUILD"
    # shellcheck disable=SC2153
    pre_install="$PRE_INSTALL"
    # shellcheck disable=SC2153
    obsolete_by="$OBSOLETE_BY"

    # Fail if no PACKAGE_NAME
    if [[ ! $package_name ]]; then
        echo "dkms.conf: Error! No 'PACKAGE_NAME' directive specified.">&2
        return_value=1
    fi

    # Fail if no PACKAGE_VERSION
    if [[ ! $package_version ]]; then
        echo "dkms.conf: Error! No 'PACKAGE_VERSION' directive specified.">&2
        return_value=1
    fi

    # Determine number of modules
    # The arrays are possibly sparse, so use the maximum index instead of the array length
    local s
    local max_index
    max_index=-1
    # shellcheck disable=SC2153
    for s in "${!BUILT_MODULE_NAME[@]}" \
        "${!BUILT_MODULE_LOCATION[@]}" \
        "${!DEST_MODULE_NAME[@]}" \
        "${!DEST_MODULE_LOCATION[@]}"; do
        ((s > max_index)) && max_index=$s
    done
    num_modules=$((max_index + 1))

    # Set module naming/location arrays
    local index
    for ((index=0; index < num_modules; index++)); do
        # Set values
        built_module_name[index]=${BUILT_MODULE_NAME[index]}
        built_module_location[index]=${BUILT_MODULE_LOCATION[index]}
        dest_module_name[index]=${DEST_MODULE_NAME[index]}
        dest_module_location[index]=${DEST_MODULE_LOCATION[index]}
        # shellcheck disable=SC2153
        case ${STRIP[index]} in
            [nN]*)
                strip[index]="no"
                ;;
            [yY]*)
                strip[index]="yes"
                ;;
            '')
                strip[index]=${strip[0]:-yes}
                ;;
        esac

        # If unset, set by defaults
        [[ ! ${built_module_name[$index]} ]] && \
            ((num_modules == 1)) && \
            built_module_name[index]=$PACKAGE_NAME
        [[ ! ${dest_module_name[$index]} ]] && \
            dest_module_name[index]=${built_module_name[index]}
        [[ ${built_module_location[$index]} && \
            ${built_module_location[$index]:(-1)} != / ]] && \
            built_module_location[index]="${built_module_location[index]}/"

        # FAIL if no built_module_name
        if [[ ! ${built_module_name[$index]} ]]; then
            echo "dkms.conf: Error! No 'BUILT_MODULE_NAME' directive specified for record #$index." >&2
            return_value=1
        fi

        # FAIL if built_module_name ends in .o or .ko
        case ${built_module_name[$index]} in
            *.o|*.ko)
                echo "dkms.conf: Error! 'BUILT_MODULE_NAME' directive ends in '.o' or '.ko' in record #$index." >&2
                return_value=1
                ;;
        esac

        # FAIL if dest_module_name ends in .o or .ko
        case ${dest_module_name[$index]} in
            *.o|*.ko)
                echo "dkms.conf: Error! 'DEST_MODULE_NAME' directive ends in '.o' or '.ko' in record #$index." >&2
                return_value=1
                ;;
        esac

        # Override location for specific distributions
        dest_module_location[index]="$(override_dest_module_location "${dest_module_location[index]}")"

        # Fail if bad DEST_MODULE_LOCATION
        case ${DEST_MODULE_LOCATION[$index]} in
            "")
                echo "dkms.conf: Error! No 'DEST_MODULE_LOCATION' directive specified for record #$index.">&2
                return_value=1
                ;;
            /kernel*)
                ;;
            /updates*)
                ;;
            /extra*)
                ;;
            *)
                echo "dkms.conf: Error! Directive 'DEST_MODULE_LOCATION' does not begin with">&2
                echo "'/kernel', '/updates', or '/extra' in record #$index.">&2
                return_value=1
            ;;
        esac
    done

    # Array length sanity check
    for s in ${#built_module_name[@]} \
        ${#built_module_location[@]} \
        ${#dest_module_name[@]} \
        ${#dest_module_location[@]}; do
        if ((s != num_modules)); then
            echo "dkms.conf: Error! Module name/location array length mismatch.">&2
            return_value=1
        fi
    done

    # Get the correct make command
    [[ ${MAKE_MATCH[0]} ]] || make_command="${MAKE[0]}"
    for ((index=0; index < ${#MAKE[@]}; index++)); do
    [[ ${MAKE[$index]} && ${MAKE_MATCH[$index]} && \
        $1 =~ ${MAKE_MATCH[$index]} ]] && \
        make_command="${MAKE[$index]}"
    done

    # Use the generic make command if not specified
    [[ ! $make_command ]] && make_command="make -C $kernel_source_dir M=$dkms_tree/$module/$module_version/build"

    # Check if clang was used to compile or lld was used to link the kernel.
    if [[ -e $kernel_source_dir/vmlinux ]]; then
      if readelf -p .comment "$kernel_source_dir/vmlinux" 2>&1 | grep -q clang; then
        make_command="${make_command} LLVM=1"
      fi
    elif [[ -e "${kernel_config}" ]]; then
      if grep -q CONFIG_CC_IS_CLANG=y "${kernel_config}"; then
        make_command="${make_command} LLVM=1"
      fi
    fi

    # Set patch_array (including kernel specific patches)
    local count
    count=0
    for ((index=0; index < ${#PATCH[@]}; index++)); do
    if [[ ${PATCH[$index]} && (! ${PATCH_MATCH[$index]} || $1 =~ ${PATCH_MATCH[$index]}) ]]; then
        patch_array[count]="${PATCH[index]}"
        count=$((count+1))
    fi
    done

    # Set build_exclude (preserve backward compatibility for OBSOLETE_BY)
    # Only set global BUILD_EXCLUSIVE_KERNEL_MAX if obsolete_by is set and no global or per-module restrictions exist
    if [[ $obsolete_by && ! $BUILD_EXCLUSIVE_KERNEL_MAX ]]; then
        # Check if we have any per-module KERNEL_MAX restrictions
        local has_per_module_max=false
        for ((i=0; i < num_modules; i++)); do
            [[ ${BUILD_EXCLUSIVE_KERNEL_MAX[i]} ]] && has_per_module_max=true && break
        done
        # Only set global if no per-module restrictions exist (backward compatibility)
        [[ $has_per_module_max == false ]] && BUILD_EXCLUSIVE_KERNEL_MAX=$obsolete_by
    fi

    # Function to check BUILD_EXCLUSIVE conditions for a specific module
    check_build_exclusive_for_module() {
        local module_index="$1"
        local kernel_version="$2"
        local arch="$3"

        # Set has_build_exclusive_directives if any BUILD_EXCLUSIVE directives are defined
        if [[ $BUILD_EXCLUSIVE_KERNEL || $BUILD_EXCLUSIVE_KERNEL_MIN || $BUILD_EXCLUSIVE_KERNEL_MAX || $BUILD_EXCLUSIVE_ARCH || $BUILD_EXCLUSIVE_CONFIG ]]; then
            has_build_exclusive_directives=true
        elif [[ ${BUILD_EXCLUSIVE_KERNEL[$module_index]} || ${BUILD_EXCLUSIVE_KERNEL_MIN[$module_index]} || ${BUILD_EXCLUSIVE_KERNEL_MAX[$module_index]} || ${BUILD_EXCLUSIVE_ARCH[$module_index]} || ${BUILD_EXCLUSIVE_CONFIG[$module_index]} ]]; then
            has_build_exclusive_directives=true
        fi

        # Check global BUILD_EXCLUSIVE directives (backward compatibility)
        [[ $BUILD_EXCLUSIVE_KERNEL && ! $kernel_version =~ $BUILD_EXCLUSIVE_KERNEL ]] && return 1
        [[ $BUILD_EXCLUSIVE_KERNEL_MIN && "$(VER "$kernel_version")" < "$(VER "$BUILD_EXCLUSIVE_KERNEL_MIN")" ]] && return 1
        [[ $BUILD_EXCLUSIVE_KERNEL_MAX && "$(VER "$kernel_version")" > "$(VER "$BUILD_EXCLUSIVE_KERNEL_MAX")" ]] && return 1
        [[ $BUILD_EXCLUSIVE_ARCH && ! $arch =~ $BUILD_EXCLUSIVE_ARCH ]] && return 1

        # Check per-module BUILD_EXCLUSIVE directives
        [[ ${BUILD_EXCLUSIVE_KERNEL[$module_index]} && ! $kernel_version =~ ${BUILD_EXCLUSIVE_KERNEL[$module_index]} ]] && return 1
        [[ ${BUILD_EXCLUSIVE_KERNEL_MIN[$module_index]} && "$(VER "$kernel_version")" < "$(VER "${BUILD_EXCLUSIVE_KERNEL_MIN[$module_index]}")" ]] && return 1
        [[ ${BUILD_EXCLUSIVE_KERNEL_MAX[$module_index]} && "$(VER "$kernel_version")" > "$(VER "${BUILD_EXCLUSIVE_KERNEL_MAX[$module_index]}")" ]] && return 1
        [[ ${BUILD_EXCLUSIVE_ARCH[$module_index]} && ! $arch =~ ${BUILD_EXCLUSIVE_ARCH[$module_index]} ]] && return 1

        # Check BUILD_EXCLUSIVE_CONFIG (both global and per-module)
        if [[ $BUILD_EXCLUSIVE_CONFIG && -e "${kernel_config}" ]]; then
            local kconf
            for kconf in $BUILD_EXCLUSIVE_CONFIG ; do
                case "$kconf" in
                    !*) grep -q "^${kconf#!}=[ym]" "${kernel_config}" && return 1 ;;
                    *)  grep -q "^${kconf}=[ym]" "${kernel_config}" || return 1 ;;
                esac
            done
        fi

        if [[ ${BUILD_EXCLUSIVE_CONFIG[$module_index]} && -e "${kernel_config}" ]]; then
            local kconf
            for kconf in ${BUILD_EXCLUSIVE_CONFIG[$module_index]} ; do
                case "$kconf" in
                    !*) grep -q "^${kconf#!}=[ym]" "${kernel_config}" && return 1 ;;
                    *)  grep -q "^${kconf}=[ym]" "${kernel_config}" || return 1 ;;
                esac
            done
        fi

        return 0
    }

    # Check if any module should be excluded from build
    build_exclude=""
    buildable_modules=0
    has_build_exclusive_directives=false

    for ((index=0; index < num_modules; index++)); do
        if check_build_exclusive_for_module "$index" "$1" "$2"; then
            ((buildable_modules++))
        fi
    done

    # If no modules can be built and BUILD_EXCLUSIVE directives are defined, set build_exclude to fail the entire build
    if [[ $buildable_modules -eq 0 && $has_build_exclusive_directives = true ]]; then
        build_exclude="yes"
    fi

    # Helper function to check yes/no values
    check_yes_no_value() {
        local var_name="$1"
        local var_value="$2"
        case "$var_value" in
            "")
                ;;
            [Yy][Ee][Ss])
                ;;
            [Nn][Oo])
                eval "$var_name=''"
                ;;
            *)
                echo "dkms.conf: Error! Unsupported $var_name value '$var_value'" >&2
                return_value=1
                ;;
        esac
    }

    # Check for allowed values in boolean variables
    check_yes_no_value "AUTOINSTALL" "$AUTOINSTALL"
    check_yes_no_value "BUILD_DEPENDS_REBUILD" "$BUILD_DEPENDS_REBUILD"
    check_yes_no_value "NO_WEAK_MODULES" "$NO_WEAK_MODULES"

    ((return_value == 0)) && last_mvka="$module/$module_version/$1/$2" && last_mvka_conf="$(readlink -f "$read_conf_file")"
    return $return_value
}

# read_conf() with additional dkms.conf checks that should prevent
# add/build/install but not uninstall/unbuild/remove of bad modules.
read_conf_strict()
{
    show_deprecation_warnings=1
    read_conf "$@" || return $?
    show_deprecation_warnings=0

    local index
    local names
    local return_value
    return_value=0

    # Warn if no modules are specified
    if ((num_modules == 0)); then
        echo "dkms.conf: Warning! Zero modules specified." >&2
    fi

    # FAIL on duplicate modules in BUILT_MODULE_NAME[]
    names=
    for ((index=0; index < num_modules; index++)); do
        local m
        m="${built_module_location[index]}${built_module_name[index]}"
        case " $names " in
            *" $m "*)
                echo "dkms.conf: Error! Duplicate module '$m' in 'BUILT_MODULE_NAME[$index]'." >&2
                return_value=1
                ;;
        esac
        names="$names $m"
    done

    # FAIL on duplicate modules in DEST_MODULE_NAME[]
    names=
    for ((index=0; index < num_modules; index++)); do
        local m
        m=${dest_module_name[index]}
        case " $names " in
            *" $m "*)
                echo "dkms.conf: Error! Duplicate module '$m' in 'DEST_MODULE_NAME[$index]'." >&2
                return_value=1
                ;;
        esac
        names="$names $m"
    done

    return $return_value
}

# Source specified variables from dkms framework configuration files.
read_framework_conf() {
    for i in /etc/dkms/framework.conf /etc/dkms/framework.conf.d/*.conf; do
        [[ -e "$i" ]] && safe_source "$i" "$@"
    done
}

# Little helper function for parsing the output of modinfo.
get_module_verinfo(){
    local ver
    local srcver
    local checksum
    local vals
    vals=
    while read -ra vals; do
        [[ ${#vals[@]} -eq 0 ]] && continue
        case "${vals[0]}" in
            version:)
                ver="${vals[1]}"
                checksum="${vals[2]}"
                ;;
            srcversion:)
                srcver="${vals[1]}"
                ;;
        esac
    done <<< "$(modinfo "$1")"

    echo -E "${ver}"
    # Use obsolete checksum info if srcversion is not available
    echo -E "${srcver:-$checksum}"
}

# Compare two modules' version
# Output:
#  "==": They are the same version and the same srcversion
#  "=": They are the same version, but not the same srcversion
#  ">": 1st one is newer than 2nd one
#  "<": 1st one is older than 2nd one
#  "?": Cannot determine
# Returns 0 if same version, otherwise 1
compare_module_version()
{
    readarray -t ver1 <<< "$(get_module_verinfo "$1")"
    readarray -t ver2 <<< "$(get_module_verinfo "$2")"
    if [[ "${ver1[0]}" = "${ver2[0]}" ]]; then
        if [[ "${ver1[1]}" = "${ver2[1]}" ]]; then
            echo "=="
        else
            echo "="
        fi
        return 0
    elif [[ ${#ver1[@]} -eq 0 ]] || [[ ${#ver2[@]} -eq 0 ]]; then
        echo "?"
    elif [[ "$(VER "${ver1[0]}")" > "$(VER "${ver2[0]}")" ]]; then
        echo ">"
    else
        echo "<"
    fi
    return 1
}

# Perform some module version sanity checking whenever we are installing
# modules.
check_version_sanity()
{
    # $1 = kernel_version
    # $2 = arch
    # $3 = obs by kernel version
    # $4 = dest_module_name

    local lib_tree
    local i
    lib_tree="$install_tree/$1"
    i=0
    if [[ $3 ]]; then
        # Magic split into array syntax saves trivial awk and cut calls.
        local -a obs
        local -a my
        local obsolete
        obs=("${3//-/ }")
        my=("${1//-/ }")
        obsolete=0
        if [[ ${#obs[@]} -gt 0 && ${#my[@]} -gt 0 ]]; then
            if [[ $(VER "${obs[0]}") == $(VER "${my[0]}") && ! $force ]]; then
                # They get obsoleted possibly in this kernel release
                if [[ ! ${obs[1]} ]]; then
                    # They were obsoleted in this upstream kernel
                    obsolete=1
                elif [[ $(VER "${my[1]}") > $(VER "${obs[1]}") ]]; then
                    # They were obsoleted in an earlier ABI bump of the kernel
                    obsolete=1
                elif [[ $(VER "${my[1]}") = $(VER "${obs[1]}") ]]; then
                    # They were obsoleted in this ABI bump of the kernel
                    obsolete=1
                fi
            elif [[ $(VER "${my[0]}") > $(VER "${obs[0]}") && ! $force ]]; then
                # They were obsoleted in an earlier kernel release
                obsolete=1
            fi
        fi

        if ((obsolete == 1)); then
            echo "" >&2
            echo "Module has been obsoleted due to being included in kernel $3." >&2
            echo "We will avoid installing for future kernels above $3." >&2
            echo "You may override by specifying --force." >&2
            return 1
        fi
    fi
    set_module_suffix "$1"
    read -ra kernels_module <<< "$(find_module "$lib_tree" "${4}")"
    [[ ${#kernels_module[@]} -eq 0 ]] && return 0

    if [[ "$force_version_override" == "true" ]]; then
        # Skip the following version checking code.
        return 0
    fi

    if [[ ${kernels_module[1]} ]]; then
        warn "Cannot do version sanity checking because multiple ${4}$module_suffix modules were found in kernel $1."
        return 0
    fi
    local dkms_module
    dkms_module=$(compressed_or_uncompressed "$dkms_tree/$module/$module_version/$1/$2/module" "${4}")

    local cmp_res
    cmp_res="$(compare_module_version "${kernels_module}" "${dkms_module}")"
    if [[ "${cmp_res}" = ">" ]]; then
        if [[ ! "$force" ]]; then
            error "Module version $(get_module_verinfo "${dkms_module}" | head -n 1) for $4${module_suffix}" \
                "is not newer than what is already found in kernel $1 ($(get_module_verinfo "${kernels_module}" | head -n 1))." \
                "You may override by specifying --force."
            return 1
        fi
    elif [[ "${cmp_res}" = "==" ]]; then
        if [[ ! "$force" ]]; then
            # if the module has neither version nor srcversion/checksum, check the binary files instead
            local verinfo
            verinfo="$(get_module_verinfo "${dkms_module}")"
            if [[ "$(echo "$verinfo" | tr -d '[:space:]')" ]] || diff "${kernels_module}" "${dkms_module}" &>/dev/null; then
                verinfo=$(echo "$verinfo" | head -n 1)
                if [[ $verinfo ]]; then
                    echo "Module ${kernels_module} already installed at version ${verinfo}, override by specifying --force" >&2
                else
                    echo "Module ${kernels_module} already installed (unversioned module), override by specifying --force" >&2
                fi
                return 1
            fi
        fi
    fi
    return 0
}

check_module_args() {
    [[ $module && $module_version ]] && return
    die 1 "Arguments <module> and <module-version> are not specified." \
        "Usage: $1 <module>/<module-version> or" \
        "       $1 -m <module>/<module-version> or" \
        "       $1 -m <module> -v <module-version>"
}

read_conf_or_die() {
    read_conf "$@" && return
    die 8 "Bad conf file."\
        "File: ${3:-$conf} does not represent a valid dkms.conf file."
}

read_conf_strict_or_die() {
    read_conf_strict "$@" && return
    die 8 "Bad conf file."\
        "File: ${3:-$conf} does not represent a valid dkms.conf file."
}

run_build_script() {
    # $1 = script type
    # $2 = script to run
    # $3 = (optional) logfile for script output
    local script_type
    local run
    [[ $2 ]] || return 0
    case "$1" in
        pre_build|post_build)
            script_type='build'
            ;;
        *)
            script_type='source'
            ;;
    esac
    run="$dkms_tree/$module/$module_version/$script_type/$2"
    if [[ -x ${run%% *} ]]; then
        if [[ $3 ]]; then
            local res
            res=0
            invoke_command "cd $dkms_tree/$module/$module_version/$script_type/ && $run" "Running the $1 script" "$3" background || res=$?
            if [[ $res != 0 ]]; then
                echo "Consult $3 for more information."
                exit "$res"
            fi
        else
        echo "Running the $1 script:"
        (
            cd "$dkms_tree/$module/$module_version/$script_type/" || exit 15
            exec $run
        )
        fi
    else
        echo ""
        warn "The $1 script is not executable."
    fi
}

# Register a DKMS-ified source tree with DKMS.
# This function is smart enough to register the module if we
# passed a source tree or a tarball instead of relying on the source tree
# being unpacked into /usr/src/$module-$module_version.
add_module()
{
    # If $archive is set and $module and $module_version are not,
    # try loading the tarball passed first.
    if [[ $archive_location && ! $module && ! $module_version ]]; then
        load_tarball
    elif [[ $try_source_tree && ! $module && ! $module_version ]]; then
        add_source_tree "$try_source_tree"
    fi

    # Check that we have all the arguments
    check_module_args add

    # Do stuff for --rpm_safe_upgrade
    if [[ $rpm_safe_upgrade ]]; then
        local pppid
        local lock_name
        pppid=$(awk '/PPid:/ {print $2}' /proc/$PPID/status)
        lock_name=$(mktemp_or_die "$tmp_location/dkms_rpm_safe_upgrade_lock.$pppid.XXXXXX")
        echo "$module-$module_version" >> "$lock_name"
        ps -o lstart --no-headers -p "$pppid" 2>/dev/null >> "$lock_name"
    fi

    # Check that this module-version hasn't already been added
    if is_module_added "$module" "$module_version"; then
        die 3 "DKMS tree already contains: $module/$module_version" \
            "You cannot add the same module/version combo more than once."
    fi

    local source_conf
    source_conf=${conf:-"$source_tree/$module-$module_version/dkms.conf"}

    # Check that /usr/src/$module-$module_version exists
    if ! [[ -d $source_tree/$module-$module_version ]]; then
        die 2 "Could not find module source directory." \
            "Directory: $source_tree/$module-$module_version does not exist."
    fi

    # Check the conf file for sanity
    read_conf_strict_or_die "$kernelver" "$arch" "$source_conf"

    # Create the necessary dkms tree structure
    echo "Creating symlink $dkms_tree/$module/$module_version/source -> $source_tree/$module-$module_version"
    mkdir -p "$dkms_tree/$module/$module_version/build"
    ln -s "$source_tree/$module-$module_version" "$dkms_tree/$module/$module_version/source"

    # Run the post_add script
    run_build_script post_add "$post_add"
}

# Prepare a kernel source or include tree for compiling a module.
# Most modern-ish distros do not require this function at all,
# so it will be removed in a future release.
prepare_kernel()
{
    # $1 = kernel version to prepare

    # Check that kernel-source exists
    _check_kernel_dir "$1" || {
        die 21 "Your kernel headers for kernel $1 cannot be found at $install_tree/$1/build or $install_tree/$1/source." \
            "Please install the linux-headers-$1 package or use the --kernelsourcedir option to tell DKMS where it's located."
    }
}

prepare_mok()
{
    if [[ ! $mok_signing_key ]]; then
        # No custom key specified, use the default key created by update-secureboot-policy for Ubuntu
        # Debian's update-secureboot-policy has no --new-key option
        case "$running_distribution" in
            ubuntu* )
                mok_signing_key="/var/lib/shim-signed/mok/MOK.priv"
                mok_certificate="/var/lib/shim-signed/mok/MOK.der"

                if [[ ! -f ${mok_signing_key} || ! -f ${mok_certificate} ]]; then
                    if [[ ! -x "$(command -v update-secureboot-policy)" ]]; then
                        echo "Binary update-secureboot-policy not found, modules won't be signed"
                        return 1
                    fi
                    # update-secureboot-policy won't create new key if $mok_certificate exists
                    if [[ -f ${mok_certificate} ]]; then
                        rm -f "${mok_certificate:?}"
                    fi
                    echo "Certificate or key are missing, generating them using update-secureboot-policy..."
                    SHIM_NOTRIGGER=y update-secureboot-policy --new-key &>/dev/null
                    update-secureboot-policy --enroll-key
                fi
                ;;
            gentoo* )
                # If the usual Gentoo/Portage environment variables are set, use those.
                mok_signing_key=${MODULES_SIGN_KEY}
                mok_certificate=${MODULES_SIGN_CERT}

                # If still empty, attempt to read the signing configuration set for portage.
                if [[ ! $mok_signing_key && -f /etc/portage/make.conf ]]; then
                    mok_signing_key=$(grep "^MODULES_SIGN_KEY=" /etc/portage/make.conf | cut -f2 -d= | sed 's/"//g')
                fi
                if [[ ! $mok_certificate && -f /etc/portage/make.conf ]]; then
                    mok_certificate=$(grep "^MODULES_SIGN_CERT=" /etc/portage/make.conf | cut -f2 -d= | sed 's/"//g')
                fi

                if [[ ! $mok_signing_key && -f ${kernel_config} ]]; then
                    mok_signing_key=$(grep "^CONFIG_MODULE_SIG_KEY=" "${kernel_config}" | cut -f2 -d= | sed 's/"//g')
                    # Kernel module signing facility requires PEM files containing both
                    # the key and the certificate, so in this case both will be the same.
                    mok_certificate=${mok_signing_key}
                fi
                ;;
        esac
    fi

    if [[ ! $mok_signing_key ]]; then
        mok_signing_key="/var/lib/dkms/mok.key"
    fi
    echo "Signing key: $mok_signing_key"

    if [[ ! $mok_certificate ]]; then
        mok_certificate="/var/lib/dkms/mok.pub"
    fi
    echo "Public certificate (MOK): $mok_certificate"

    # scripts/sign-file.c in kernel source also supports using "pkcs11:..." as private key
    if [[ $mok_signing_key != "pkcs11:"* ]] && [[ ! -f $mok_signing_key || ! -f $mok_certificate ]]; then
        echo "Certificate or key are missing, generating self signed certificate for MOK..."
        if ! command -v openssl >/dev/null; then
            echo "openssl not found, can't generate key and certificate."
            return 1
        fi
        # Requires OpenSSL >= 1.1.1 for -addext
        openssl req -new -x509 -nodes -days 36500 -subj "/CN=DKMS module signing key" \
            -newkey rsa:2048 -keyout "$mok_signing_key" \
            -addext "extendedKeyUsage=codeSigning" \
            -outform DER -out "$mok_certificate" > /dev/null 2>&1
        if [[ ! -f ${mok_signing_key} ]]; then
            echo "Key file ${mok_signing_key} not found and can't be generated, modules won't be signed"
            return 1
        fi
    fi

    if [[ ! -f ${mok_certificate} ]]; then
        echo "Certificate file ${mok_certificate} not found and can't be generated, modules won't be signed"
        return 1
    fi

    return 0
}

prepare_signing()
{
    # Lazy source in signing related configuration
    read_framework_conf "$dkms_framework_signing_variables"

    do_signing=0

    if [[ ! -f ${kernel_config} ]]; then
        echo "Kernel config ${kernel_config} not found, modules won't be signed"
        return
    fi

    if ! grep -q "^CONFIG_MODULE_SIG_HASH=" "${kernel_config}"; then
        echo "The kernel is built without module signing facility, modules won't be signed"
        return
    fi

    sign_hash=$(grep "^CONFIG_MODULE_SIG_HASH=" "${kernel_config}" | cut -f2 -d= | sed 's/"//g')

    if [[ ! ${sign_file} ]]; then
        case "$running_distribution" in
            debian* )
                sign_file="/usr/lib/linux-kbuild-${kernelver%.*}/scripts/sign-file"
                ;;
            ubuntu* )
                sign_file="$(command -v kmodsign)"
                if [[ ! -x "${sign_file}" ]]; then
                    sign_file="/usr/src/linux-headers-$kernelver/scripts/sign-file"
                fi
                ;;
            gentoo* )
                sign_file="/usr/src/linux-$kernelver/scripts/sign-file"
                ;;
        esac
        if [[ ! -f ${sign_file} ]]; then
            sign_file="$install_tree/$kernelver/build/scripts/sign-file"
        fi
    fi
    echo "Sign command: $sign_file"

    if [[ ! -f ${sign_file} || ! -x ${sign_file} ]]; then
        echo "Binary ${sign_file} not found, modules won't be signed"
        return
    fi

    if prepare_mok; then
        do_signing=1
    fi
}

# Build a module that has been registered with DKMS.
do_build()
{
    [[ $kernelver && $arch ]] || die 16 "do_build: Empty \$kernelver or \$arch"

    # If the module has not been added, try to add it.
    if ! is_module_added "$module" "$module_version" ; then
        add_module
        echo ""
    fi

    local -r source_dir="$dkms_tree/$module/$module_version/source"
    local -r build_dir="$dkms_tree/$module/$module_version/build"
    local -r kernelver_dir="$dkms_tree/$module/$module_version/$kernelver"
    local -r base_dir="$kernelver_dir/$arch"
    local -r build_log="$build_dir/make.log"
    local bd
    local bd_missing
    local status
    local mvka
    local count
    local index

    # Check that the module has not already been built for this kernel
    [[ -d $base_dir ]] && die 3 \
        "This module/version has already been built on: $kernelver" \
        "Directory $base_dir already exists. Use the dkms remove function before trying to build again."

    # Read the conf file
    set_module_suffix "$kernelver"
    read_conf_strict_or_die "$kernelver" "$arch"

    # Error out if build_exclude is set
    [[ $build_exclude ]] && diewarn 77 \
        "The $base_dir/dkms.conf"\
        "for module $module/$module_version includes BUILD_EXCLUSIVE directives"\
        "which do not match this kernel/arch/config for any modules."\
        "This indicates that it should not be built."

    # Error out if source_tree is basically empty (binary-only dkms tarball w/ --force check)
    # shellcheck disable=SC1083,SC2012
    # TODO: use find instead of ls
    (($(ls "$source_dir" | wc -l | awk {'print $1'}) < 2)) && die 8 \
        "The directory $source_dir does not appear to have module source located within it."\
        "Build halted."

    prepare_kernel_and_signing

    if [[ -f $kernel_source_dir/.kernelvariables ]]; then
        CC=$(echo -e "show-%:\n\t@echo \$(\$*)\ninclude $kernel_source_dir/.kernelvariables" | make -f - show-CC)
        export CC
    else
        unset CC
    fi

    if [[ -e "${kernel_config}" ]]; then
        local cc
        # Depending on the kernel version the strings might be formatted differently (with or without quotes):
        #
        # $ grep CONFIG_CC_VERSION_TEXT= /lib/modules/5.15.0-141-generic/build/.config
        # CONFIG_CC_VERSION_TEXT="gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0"
        # $ grep CONFIG_CC_VERSION_TEXT= /lib/modules/5.15.0-141-generic/build/include/config/auto.conf
        # CONFIG_CC_VERSION_TEXT="gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0"
        # $ grep CONFIG_CC_VERSION_TEXT= /lib/modules/6.8.0-60-generic/build/.config
        # CONFIG_CC_VERSION_TEXT="x86_64-linux-gnu-gcc-12 (Ubuntu 12.3.0-1ubuntu1~22.04) 12.3.0"
        # $ grep CONFIG_CC_VERSION_TEXT= /lib/modules/6.8.0-60-generic/build/include/config/auto.conf
        # CONFIG_CC_VERSION_TEXT=x86_64-linux-gnu-gcc-12 (Ubuntu 12.3.0-1ubuntu1~22.04) 12.3.0
        cc=$(sed -n 's|^CONFIG_CC_VERSION_TEXT="*\([^" ]*\) .*|\1|p' "${kernel_config}")
        if command -v "$cc" >/dev/null; then
            CC="$cc"
            KERNEL_CC="$cc"
            export CC
            export KERNEL_CC
        fi

        if grep -q 'CONFIG_CC_IS_CLANG=y' "${kernel_config}"; then
            local cc
            cc=clang
            if command -v "$cc" >/dev/null; then
                CC="$cc"
                KERNEL_CC="$cc"
                export CC
                export KERNEL_CC
            fi
        fi

        if grep -q 'CONFIG_LD_IS_LLD=y' "${kernel_config}"; then
            local ld
            ld=ld.lld
            if command -v "$ld" >/dev/null; then
                LD="$ld"
                KERNEL_LD="$ld"
                export LD
                export KERNEL_LD
            fi
        fi
    fi

    # Source build environment file if specified
    if [[ -n "$build_environment" && -f "$build_environment" ]]; then
        # shellcheck disable=SC1090
        source <( (source "$build_environment" &>/dev/null; export -p) )
    fi

    # Check for missing BUILD_DEPENDS
    bd_missing=
    # shellcheck disable=SC2153
    for bd in "${BUILD_DEPENDS[@]}"; do
        while read -r status mvka; do
            [[ $status ]] || continue
            [[ $status = installed ]] && continue 2
        done <<< "$(module_status "$bd" "*" "$kernelver" "$arch")"
        bd_missing="$bd_missing $bd"
    done
    if [[ $bd_missing ]]; then
        if [[ $force ]]; then
            warn "Trying to build module $module/$module_version for kernel $kernelver ($arch) despite of missing BUILD_DEPENDS:$bd_missing."
        else
            die 13 "Aborting build of module $module/$module_version for kernel $kernelver ($arch) due to missing BUILD_DEPENDS:$bd_missing."\
                "You may override by specifying --force."
        fi
    fi

    # Set up temporary build directory for build
    rm -rf "${build_dir:?}"
    CP -a "$source_dir/" "$build_dir"

    echo "DKMS (@RELEASE_STRING@) make.log for $module/$module_version for kernel $kernelver ($arch)" >> "$build_log"
    date >> "$build_log"

    cd "$build_dir" || exit 15

    # Apply any patches
    for p in "${patch_array[@]}"; do
        [[ patches/$p == *"/../"* ]] && \
            report_build_problem 5 \
            "Patch $p as specified in dkms.conf contains '..' path component."
        [[ ! -e $build_dir/patches/$p ]] && \
            report_build_problem 5 \
            "Patch $p as specified in dkms.conf cannot be" \
            "found in $build_dir/patches/."
        invoke_command "patch -p1 < ./patches/$p" "Applying patch $p" "$build_log" background || \
            report_build_problem 6 "Application of patch $p failed." \
            "Consult $build_log for more information."
    done

    # Run the pre_build script
    run_build_script pre_build "$pre_build" "$build_log"

    local the_make_command
    the_make_command="${make_command/#make/make -j$parallel_jobs KERNELRELEASE=$kernelver}"

    invoke_command "$the_make_command" "Building module(s)" "$build_log" background || \
        report_build_problem 10 "Bad return status for module build on kernel: $kernelver ($arch)" \
        "Consult $build_log for more information."

    # Make sure all the modules built successfully (skip excluded modules)
    for ((count=0; count < num_modules; count++)); do
        # Skip modules that are excluded due to BUILD_EXCLUSIVE restrictions
        if ! check_build_exclusive_for_module "$count" "$kernelver" "$arch"; then
            continue
        fi

        [[ -e ${built_module_location[$count]}${built_module_name[$count]}$module_uncompressed_suffix ]] && continue
        report_build_problem 7 \
            "Build of ${built_module_location[$count]}${built_module_name[$count]}$module_uncompressed_suffix failed for: $kernelver ($arch)" \
            "Make sure the name and location of the generated module are correct," \
            "or consult $build_log for more information."
    done

    # Build success, so create DKMS structure for a built module
    mkdir -p "$kernelver_dir"
    local -r tmp_base_dir=$(mktemp_or_die -d "$kernelver_dir/.tmp_${arch}_XXXXXX")
    mkdir -p "$tmp_base_dir/log"

    # Save a copy of the new module
    mkdir -p "$tmp_base_dir/module"
    if [[ -f "$build_dir/Module.symvers" ]] ; then
        CP -f "$build_dir/Module.symvers" "$tmp_base_dir/module/Module.symvers"
    fi
    for ((count=0; count < num_modules; count++)); do
        # Skip modules that are excluded due to BUILD_EXCLUSIVE restrictions
        if ! check_build_exclusive_for_module "$count" "$kernelver" "$arch"; then
            continue
        fi

        local the_module
        local built_module
        local compressed_module
        the_module="$build_dir/${built_module_location[$count]}${built_module_name[$count]}"
        built_module="$the_module$module_uncompressed_suffix"
        compressed_module="$the_module$module_suffix"

        if [[ ! -f $built_module ]]; then
            warn "$built_module has disappeared"
            continue
        fi

        if [[ ${strip[$count]} != no ]] && [[ ${CC} == "clang" ]]; then
            llvm-strip -g "$built_module"
        elif [[ ${strip[$count]} != no ]]; then
            strip -g "$built_module"
        fi

        if (( do_signing )); then
            echo "Signing module $built_module"
            if ! signing_command_output=$("$sign_file" "$sign_hash" "$mok_signing_key" "$mok_certificate" "$built_module" 2>&1); then
                warn "Failed to sign module '$built_module'!" "$signing_command_output"
            fi
        fi

        if [[ $module_compressed_suffix = .gz ]]; then
            # shellcheck disable=SC2086
            # splitting is intentional
            gzip $compress_gzip_opts -f "$built_module" || compressed_module=""
        elif [[ $module_compressed_suffix = .xz ]]; then
            # shellcheck disable=SC2086
            # splitting is intentional
            xz $compress_xz_opts -f "$built_module" || compressed_module=""
        elif [[ $module_compressed_suffix = .zst ]]; then
            # shellcheck disable=SC2086
            # splitting is intentional
            zstd $compress_zstd_opts -f "$built_module" || compressed_module=""
        fi
        if [[ $compressed_module ]]; then
            CP -f "$compressed_module" "$tmp_base_dir/module/${dest_module_name[$count]}$module_suffix"
        else
            CP -f "$built_module" "$tmp_base_dir/module/${dest_module_name[$count]}$module_uncompressed_suffix"
        fi
    done

    # Validate build completeness (skip excluded modules)
    for ((index=0; index < num_modules; index++)); do
        # Skip modules that are excluded due to BUILD_EXCLUSIVE restrictions
        if ! check_build_exclusive_for_module "$index" "$kernelver" "$arch"; then
            continue
        fi

        local m
        local f
        m=${dest_module_name[index]}
        f=$(compressed_or_uncompressed "$tmp_base_dir/module" "$m")
        [[ $f && -f $f ]] || die 7 "Missing module '$m' in $tmp_base_dir/module"
    done

    # Rename the temporary directory
    chmod 0755 "$tmp_base_dir"
    mv -T "$tmp_base_dir" "$base_dir" || die 7 "do_build: final mv failed ($?)"

    # Run the post_build script, requires accessible $mvka/module/
    run_build_script post_build "$post_build" "$build_log"

    cd - >/dev/null || exit 15

    # Save the log file
    mv -f "$build_log" "$base_dir/log/make.log" 2>/dev/null

    # Clean the build directory
    rm -rf "${build_dir:?}"

    # After successful build, save dependency versions
    if [[ $BUILD_DEPENDS_REBUILD ]] && [[ ! $force ]]; then
        mkdir -p "$base_dir"
        for bd in "${BUILD_DEPENDS[@]}"; do
            local dep_version
            dep_version=$(module_status "$bd" "*" "$kernelver" "$arch" | while read -r status mvka; do
                [[ $status ]] || continue
                IFS='/' read -r m v k a <<< "$mvka"
                echo "$v"
            done | sort -V | tail -n1)
            [[ $dep_version ]] && echo "$dep_version" > "$base_dir/.dep_${bd}"
        done
    fi
}

prepare_kernel_and_signing()
{
    [[ $prepared_kernel = "$kernelver/$arch" ]] && return

    set_kernel_source_dir_and_kconfig "$kernelver"
    prepare_kernel "$kernelver"
    prepare_signing
    prepared_kernel="$kernelver/$arch"
    echo ""
}

# Force the installation of a module if this is listed
# in the files in $forced_modules_dir, if any
force_installation()
{
    forced_modules_dir="/usr/share/dkms/modules_to_force_install"
    to_force=""
    if [[ -d $forced_modules_dir ]]; then
        for elem in "$forced_modules_dir/"*; do
            if [[ -e $elem ]]; then
                to_force="$to_force $(cat "$elem")"
            fi
        done

        for elem in $to_force; do
            if [[ ${1} = "${elem}" ]]; then
                echo "force"
                return 0
            elif [[ ${1}_version-override = "${elem}" ]]; then
                echo "version-override"
                return 0
            fi
        done
    fi
    return 1
}

# Install a previously built module
# There are huge swaths of code here that special-case for various distros.
# They should be split into their own functions.
do_install()
{
    [[ $kernelver && $arch ]] || die 16 "do_install: Empty \$kernelver or \$arch"

    # If the module has not been built, try to build it first.
    is_module_built "$module" "$module_version" "$kernelver" "$arch" || do_build

    local -r base_dir="$dkms_tree/$module/$module_version/$kernelver/$arch"
    local -r lib_tree="$install_tree/$kernelver"
    local -r original_module_backup_dir="$dkms_tree/$module/original_module/$kernelver/$arch"
    local -r active_link="$dkms_tree/$module/kernel-$kernelver-$arch"
    local count
    local index
    local symlink
    [[ $symlink_modules ]] && symlink="-s"

    # Save the status of $force
    local -r tmp_force="$force"

    # If the module is set to be force-installed
    local ret
    ret=$(force_installation "$module")
    if [[ "$ret" == "force" ]]; then
        force="true"
        echo "Forcing installation of $module"
    elif [[ "$ret" == "version-override" ]]; then
        force_version_override="true"
        echo "Forcing version override of $module"
    fi

    # Make sure that kernel exists to install into
    [[ -e $install_tree/$kernelver ]] || die 6 \
        "The directory $install_tree/$kernelver doesn't exist." \
        "You cannot install a module onto a non-existant kernel."

    # Read the conf file
    set_module_suffix "$kernelver"
    read_conf_strict_or_die "$kernelver" "$arch"

    # Validate build completeness (skip excluded modules)
    for ((index=0; index < num_modules; index++)); do
        # Skip modules that are excluded due to BUILD_EXCLUSIVE restrictions
        if ! check_build_exclusive_for_module "$index" "$kernelver" "$arch"; then
            continue
        fi

        local m
        local f
        m=${dest_module_name[index]}
        f=$(compressed_or_uncompressed "$base_dir/module" "$m")
        [[ $f && -f $f ]] || die 6 "Missing module $m in $base_dir/module"
    done

    # Check that its not already installed (kernel symlink)
    is_module_installed "$module" "$module_version" "$kernelver" "$arch" && die 5 \
        "This module/version combo is already installed for kernel $kernelver ($arch)."

    # If upgrading using rpm_safe_upgrade, go ahead and force the install
    # else we can wind up with the first half of an upgrade failing to install anything,
    # while the second half of the upgrade, the removal, then succeeds, leaving us with
    # nothing installed.
    [[ $rpm_safe_upgrade ]] && force="true"

    # Save the original_module if one exists, none have been saved before, and this is the first module for this kernel
    local any_module_installed

    if ! run_build_script pre_install "$pre_install" && ! [[ $force ]]; then
        die 101 "pre_install failed, aborting install." \
            "You may override by specifying --force."
    fi

    for ((count=0; count < num_modules; count++)); do
        # Skip modules that are excluded due to BUILD_EXCLUSIVE restrictions
        if ! check_build_exclusive_for_module "$count" "$kernelver" "$arch"; then
            continue
        fi

        # Check this version against what is already in the kernel
        check_version_sanity "$kernelver" "$arch" "$obsolete_by" "${dest_module_name[$count]}" || continue

        local m
        local installed_modules
        local module_count
        local original_copy
        m=${dest_module_name[$count]}
        installed_modules=$(find_module "$lib_tree" "$m")
        module_count=${#installed_modules[@]}
        original_copy=$(compressed_or_uncompressed "$original_module_backup_dir" "$m")
        if [[ -L $active_link && $original_copy ]]; then
            echo "An original module was already stored during a previous install"
        elif [[ ! -L $active_link ]]; then
            local original_module
            local found_original
            local archive_pref1
            local archive_pref2
            local archive_pref3
            local archive_pref4
            original_module=""
            found_original=""
            archive_pref1=$(compressed_or_uncompressed "$lib_tree/extra" "$m")
            archive_pref2=$(compressed_or_uncompressed "$lib_tree/updates" "$m")
            archive_pref3=$(compressed_or_uncompressed "$lib_tree${dest_module_location[$count]}" "$m")
            archive_pref4=""
            ((module_count == 1)) && archive_pref4=${installed_modules[0]}
            for original_module in $archive_pref1 $archive_pref2 $archive_pref3 $archive_pref4; do
                [[ -f $original_module ]] || continue
                echo "Found pre-existing $original_module, archiving for uninstallation"
                mkdir -p "$original_module_backup_dir"
                mv -f "$original_module" "$original_module_backup_dir/"
                echo "$original_module" > "$original_module_backup_dir/${original_module##*/}.origin"
                found_original="yes"
                break
            done
            if [[ ! $found_original ]] && ((module_count > 1)); then
                echo "Multiple original modules exist, so none will be put back in place during a later uninstall"
                echo "All instances will be stored for reference purposes in $original_module_backup_dir/collisions/"
            fi
        fi

        if ((module_count > 1)); then
            echo "Multiple same named modules! $module_count named $m$module_suffix in $lib_tree/"
            for module_dup in $(find_module "$lib_tree" "$m"); do
                dup_tree="${module_dup#"${lib_tree}"}";
                dup_name="${module_dup##*/}"
                dup_tree="${dup_tree/"${dup_name}"}"
                mkdir -p "$original_module_backup_dir/collisions/$dup_tree"
                mv -f "$module_dup" "$original_module_backup_dir/collisions/$dup_tree/"
                echo "$module_dup" > "$original_module_backup_dir/collisions/$dup_tree/$dup_name.origin"
            done
        fi

        # Copy module to its location
        local toinstall
        local dest_dir
        local dest_name
        toinstall=$(compressed_or_uncompressed "$base_dir/module" "$m")
        dest_dir="$install_tree/$kernelver${dest_module_location[$count]}"
        dest_name=${toinstall##*/}
        echo "Installing $dest_dir/$dest_name"
        mkdir -p "$dest_dir"
        CP -f $symlink "$toinstall" "$dest_dir/$dest_name" || die 6 "Copying '$toinstall' failed"
        any_module_installed=1

    done

    if ((num_modules > 0)) && [[ ! "${any_module_installed}" ]]; then
        die 6 "Installation aborted."
    fi

    # Create the kernel-<kernelver> symlink to designate this version as active
    rm -f "$active_link"
    ln -s "$module_version/$kernelver/$arch" "$active_link"

    # Add to kabi-tracking
    if [[ ! $NO_WEAK_MODULES ]]; then
        if [[ ${weak_modules_add} ]]; then
            echo "Adding linked weak modules..."
            list_each_installed_module "$module" "$kernelver" "$arch" | ${weak_modules_add}
        fi
    fi

    # Run the post_install script
    run_build_script post_install "$post_install"

    invoke_command "do_depmod $kernelver" "Running depmod" '' background || {
        do_uninstall "$kernelver" "$arch"
        die 6 "Problems with depmod detected. Automatically uninstalling this module." \
            "Install Failed (depmod problems). Module rolled back to built state."
        exit 6
    }

    if [[ $modprobe_on_install ]] && [[ $kernelver = "$(uname -r)" ]]; then
        # Make the newly installed modules available immediately
        find /sys/devices -name modalias -print0 | xargs -0 cat | sort -u | xargs modprobe -a -b -q
        if [[ -f /lib/systemd/system/systemd-modules-load.service ]]; then
            systemctl restart systemd-modules-load.service
        fi
    fi

    # Restore the status of $force
    force="$tmp_force"

    # First check and rebuild any modules with updated dependencies
    check_and_rebuild_dependent_modules "${kernelver[0]}" "${arch[0]}"
}

# List each kernel object that has been installed for a particular module.
list_each_installed_module()
{
    # $1 = module
    # $2 = kernel version
    # $3 = arch
    local count
    local real_dest_module_location
    local mod
    for ((count=0; count < num_modules; count++)); do
        real_dest_module_location="$(find_actual_dest_module_location "$1" "$count" "$2" "$3")"
        mod=$(compressed_or_uncompressed "$install_tree/$2${real_dest_module_location}" "${dest_module_name[$count]}")
        echo "$mod"
    done
}

# Check if either the module source, or the symlink pointing to it is missing
# A module can only be in this broken state, if the user or a faulty program
# messed up. The module then is considered volatile, because there is no reliable
# way to tell if files in the source tree are still in a valid state.
# Therefore any action (except 'add', if only the symlink is missing)
# to operate on the module has to be refused.
# Manual intervention by the user is required to return to a sane state.
is_module_broken() {
    [[ $1 && $2 ]] || return 1
    [[ -d $dkms_tree/$1/$2 ]] || return 2
    [[ -L $dkms_tree/$1/$2/source && ! -d $dkms_tree/$1/$2/source ]] && return
    [[ ! -L $dkms_tree/$1/$2/source && -d $source_tree/$1-$2/ ]] && return
}

is_module_added() {
    [[ $1 && $2 ]] || return 1
    [[ -d $dkms_tree/$1/$2 ]] || return 2
    [[ -L $dkms_tree/$1/$2/source && -d $dkms_tree/$1/$2/source ]] || return 2
}

is_module_built() {
    [[ $1 && $2 && $3 && $4 ]] || return 1
    local d
    d="$dkms_tree/$1/$2/$3/$4"
    [[ -d $d/module ]] || return 1
}

# This assumes we have already checked to see if the module has been built.
_is_module_installed() {
    [[ $1 && $2 && $3 && $4 ]] || return 1
    local d
    local k
    d="$dkms_tree/$1/$2/$3/$4"
    k="$dkms_tree/$1/kernel-$3-$4"
    [[ -L $k && $(readlink -f "$k") = "$d" ]]
}

# This does not.
is_module_installed() { is_module_built "$@" && _is_module_installed "$@"; }

maybe_add_module() (
    is_module_added "$1" "$2" && {
        echo "Module $1/$2 already added."
        return 0
    }
    module="$1" module_version="$2" add_module
)

maybe_build_module() (
    is_module_built "$1" "$2" "$3" "$4" && {
        if [[ "$force" = "true" ]]; then
            do_unbuild "$3" "$4"
        else
            echo "Module $1/$2 already built for kernel $3 ($4), skip." \
                 "You may override by specifying --force."
            return 0
        fi
    }
    module="$1" module_version="$2" kernelver="$3" arch="$4" do_build
)

maybe_install_module() (
    is_module_installed "$1" "$2" "$3" "$4" && {
        if [[ "$force" = "true" ]]; then
            do_uninstall "$3" "$4"
            echo ""
        else
            echo "Module $1/$2 already installed on kernel $3 ($4), skip." \
                 "You may override by specifying --force."
            return 0
        fi
    }
    module="$1" module_version="$2" kernelver="$3" arch="$4" do_install
)

build_module() {
    local i
    for ((i=0; i < ${#kernelver[@]}; i++)); do
        maybe_build_module "$module" "$module_version" "${kernelver[$i]}" "${arch[$i]}"
    done
}

install_module() {
    local i
    for ((i=0; i < ${#kernelver[@]}; i++)); do
        maybe_install_module "$module" "$module_version" "${kernelver[$i]}" "${arch[$i]}"
    done
}

possible_dest_module_locations()
{
    # $1 = count
    # There are two places an installed module may really be:
    # 1) "$install_tree/$kernelver/${dest_module_location[$count]}/${dest_module_name[$count]}$module_suffix"
    # 2) "$install_tree/$kernelver/${DEST_MODULE_LOCATION[$count]}/${dest_module_name[$count]}$module_suffix"
    # override_dest_module_location() is what controls whether or not they're the same.

    local location
    location[0]="${dest_module_location[$count]}"
    [[ ${DEST_MODULE_LOCATION[$count]} != "${dest_module_location[$count]}" ]] && \
    location[1]="${DEST_MODULE_LOCATION[$count]}"

    echo "${location[@]}"
}

find_actual_dest_module_location()
{
    local module
    local count
    local kernelver
    local arch
    local locations
    module=$1
    count=$2
    kernelver=$3
    arch=$4
    locations=$(possible_dest_module_locations "$count")

    local l
    local dkms_owned
    local installed
    dkms_owned=$(compressed_or_uncompressed "${dkms_tree}/${module}/kernel-${kernelver}-${arch}/module" "${dest_module_name[$count]}")

    for l in $locations; do
        installed=$(compressed_or_uncompressed "${install_tree}/${kernelver}${l}" "${dest_module_name[${count}]}")
        if [[ $installed ]] && compare_module_version "${dkms_owned}" "${installed}" &>/dev/null; then
            echo "${l}"
            return 0
        fi
    done

}

# Remove compiled DKMS modules from any kernels they are installed in.
do_uninstall()
{
    # $1 = kernel version
    # $2 = arch

    local -r original_module_backup_dir="$dkms_tree/$module/original_module/$1/$2"

    echo "Module $module/$module_version for kernel $1 ($2):"

    # Read the conf file
    set_module_suffix "$1"
    read_conf_or_die "$1" "$2"

    # If kernel-<kernelver> symlink points to this module, check for original_module and put it back
    local was_active
    local kernel_symlink
    local real_dest_module_location
    was_active=""
    kernel_symlink=$(readlink -f "$dkms_tree/$module/kernel-$1-$2")
    if [[ $kernel_symlink = $dkms_tree/$module/$module_version/$1/$2 ]]; then
        was_active="true"
        echo "Before uninstall, this module version was ACTIVE on this kernel."
        # remove kabi-tracking if last instance removed
        if [[ ! $NO_WEAK_MODULES ]]; then
            if [[ ${weak_modules_remove} ]] && (module_status_built "$module" "$module_version" | grep -q "installed"); then
                echo "Removing linked weak modules..."
                list_each_installed_module "$module" "$1" "$2" | ${weak_modules_remove}
            fi
        fi

        for ((count=0; count < num_modules; count++)); do
            local m
            m=${dest_module_name[$count]}

            real_dest_module_location="$(find_actual_dest_module_location "$module" "$count" "$1" "$2")"

            if [[ ${real_dest_module_location} ]]; then
                echo "Deleting $install_tree/$1${real_dest_module_location}/$m$module_suffix"
                rm -f "${install_tree:?}/$1${real_dest_module_location}/$m$module_uncompressed_suffix"*
                dir_to_remove="${real_dest_module_location#/}"
                while [[ ${dir_to_remove} != "${dir_to_remove#/}" ]]; do
                    dir_to_remove="${dir_to_remove#/}"
                done

                case "$running_distribution" in
                    debian* | ubuntu* | arch* | gentoo*)
                        (if cd "$install_tree/$1"; then rmdir -p --ignore-fail-on-non-empty "${dir_to_remove}"; fi || true)
                        ;;
                    *)
                        (if cd "$install_tree/$1"; then rpm -qf "${dir_to_remove}" >/dev/null 2>&1 || rmdir -p --ignore-fail-on-non-empty "${dir_to_remove}"; fi || true)
                        ;;
                esac
            else
                echo "Module $m$module_suffix was not found within $install_tree/$1/"
            fi
            if [[ -d $original_module_backup_dir ]]; then
                local origin_file
                while IFS= read -r origin_file
                do
                    [[ $origin_file ]] || continue
                    local original_module
                    original_module=${origin_file%.origin}
                    [[ -f $original_module ]] || continue
                    local original_location
                    original_location=$(head -n 1 "$origin_file")
                    case $original_location in
                        "$install_tree/$1/"*)
                            echo "Restoring archived original module $original_location"
                            mkdir -p "${original_location%/*}"
                            mv -f "$original_module" "$original_location"
                            rm -f "$origin_file"
                            ;;
                        *)  warn "Bad original location '$original_location' in $origin_file"
                            ;;
                    esac
                done <<< "$(find "$original_module_backup_dir" -type f -name "$m.*.origin")"
                find "${original_module_backup_dir%/*}" -type d -empty -delete
            fi
            local origmod
            origmod=$(compressed_or_uncompressed "$original_module_backup_dir" "$m")
            if [[ $origmod ]]; then
                # Module was archived by older dkms versions without creating .origin
                echo "Restoring archived original module"
                mkdir -p "$install_tree/$1${DEST_MODULE_LOCATION[$count]}/"
                mv -f "$origmod" "$install_tree/$1${DEST_MODULE_LOCATION[$count]}/" 2>/dev/null
            fi
        done
        rm -f "${dkms_tree:?}/$module/kernel-$1-$2"
    else
        echo "This module version was INACTIVE for this kernel."
    fi

    # Run the post_remove script
    run_build_script post_remove "$post_remove"

    # Run depmod because we changed $install_tree
    if [[ ! $delayed_depmod ]]; then
        invoke_command "do_depmod $1" "Running depmod" '' background
    else
        touch "$dkms_tree/depmod-pending-$1-$2"
    fi

    # Delete the original_module if nothing for this kernel is installed anymore
    if [[ $was_active && -d $dkms_tree/$module/original_module/$1/$2 ]]; then
        echo "Removing original module(s) from DKMS tree for kernel $1 ($2)"
        rm -rf "${dkms_tree:?}/$module/original_module/$1/$2" 2>/dev/null
        [[ $(find "$dkms_tree/$module/original_module/$1/"* -maxdepth 0 -type d 2>/dev/null) ]] || rm -rf "${dkms_tree:?}/$module/original_module/$1"
    fi
    [[ $(find "$dkms_tree/$module/original_module/"* -maxdepth 0 -type d 2>/dev/null) ]] || rm -rf "${dkms_tree:?}/$module/original_module"
}

module_is_broken_and_die() {
    is_module_broken "$module" "$module_version" && die 4 "$module/$module_version is broken!"\
        "Missing the source directory or the symbolic link pointing to it."\
        "Manual intervention is required!"
}

module_is_added_or_die()
{
    is_module_added "$module" "$module_version" || die 3 \
        "The module/version combo: $module/$module_version is not located in the DKMS tree."
}

maybe_unbuild_module()
{
    is_module_built "$module" "$module_version" "$1" "$2" || {
        echo "Module $module/$module_version is not built for kernel $1 ($2)."\
            "Skipping..."
        return 0
    }

    do_unbuild "$1" "$2"
}

maybe_uninstall_module()
{
    is_module_installed "$module" "$module_version" "$1" "$2" || {
        # Check if this module might have been excluded due to BUILD_EXCLUSIVE restrictions
        # If so, don't show the "not installed" message since it's expected
        local was_excluded=false

        # Try to read the dkms.conf to check BUILD_EXCLUSIVE settings
        if [[ -f "$dkms_tree/$module/$module_version/source/dkms.conf" ]]; then
            # Temporarily read the conf to check BUILD_EXCLUSIVE settings
            local saved_last_mvka="$last_mvka"
            local saved_last_mvka_conf="$last_mvka_conf"
            local saved_num_modules="$num_modules"

            # Read conf without modifying global state permanently
            read_conf "$1" "$2" "$dkms_tree/$module/$module_version/source/dkms.conf" 2>/dev/null || true

            # If this package has any BUILD_EXCLUSIVE restrictions, suppress the message
            # because it can be confusing to users (modules might be built but not installed)
            local has_build_exclusive=false
            for ((index=0; index < num_modules; index++)); do
                if [[ ${BUILD_EXCLUSIVE_KERNEL[index]} || ${BUILD_EXCLUSIVE_KERNEL_MIN[index]} || ${BUILD_EXCLUSIVE_KERNEL_MAX[index]} || ${BUILD_EXCLUSIVE_ARCH[index]} || ${BUILD_EXCLUSIVE_CONFIG[index]} ]]; then
                    has_build_exclusive=true
                    break
                fi
            done

            if [[ $has_build_exclusive == true ]]; then
                was_excluded=true
            fi

            # Restore global state
            last_mvka="$saved_last_mvka"
            last_mvka_conf="$saved_last_mvka_conf"
            num_modules="$saved_num_modules"
        fi

        # Only show the "not installed" message if it wasn't excluded due to BUILD_EXCLUSIVE
        if [[ $was_excluded == false ]]; then
            echo "Module $module/$module_version is not installed for kernel $1 ($2)."\
                "Skipping..."
        fi
        return 0
    }
    do_uninstall "$1" "$2"
}

uninstall_module()
{
    local i
    for ((i=0; i < ${#kernelver[@]}; i++)); do
        [[ $i = 0 ]] || echo ""
        maybe_uninstall_module "${kernelver[$i]}" "${arch[$i]}"
    done
}

do_unbuild()
{
    # Delete or "unbuild" the $kernel_version/$arch_used part of the tree
    rm -rf "${dkms_tree:?}/$module/$module_version/$1/$2"
    [[ $(find "${dkms_tree:?}/$module/$module_version/$1/"* -maxdepth 0 -type d 2>/dev/null) ]] || \
        rm -rf "${dkms_tree:?}/$module/$module_version/$1"
}

# Remove the built module, w/o removing/unregistering it.
# This uninstalls any installed modules along the way
unbuild_module()
{
    local i
    for ((i=0; i < ${#kernelver[@]}; i++)); do
        [[ $i = 0 ]] || echo ""
        maybe_uninstall_module "${kernelver[$i]}" "${arch[$i]}"
        maybe_unbuild_module "${kernelver[$i]}" "${arch[$i]}"
    done
}

# Unregister a DKMS module.  This uninstalls any installed modules along the way.
remove_module()
{
    # Clean up leftover temporary directories
    rm -rf "${dkms_tree:?}/$module/$module_version"/*/.tmp_*
    find "$dkms_tree/$module/$module_version/" -maxdepth 1 -type d -empty -delete

    # Do --rpm_safe_upgrade check (exit out and don't do remove if inter-release RPM upgrade scenario occurs)
    if [[ $rpm_safe_upgrade ]]; then
        local pppid
        local time_stamp
        pppid=$(awk '/PPid:/ {print $2}' /proc/$PPID/status)
        time_stamp=$(ps -o lstart --no-headers -p "$pppid" 2>/dev/null)
        for lock_file in "$tmp_location/dkms_rpm_safe_upgrade_lock.$pppid".*; do
            [[ -f $lock_file ]] || continue
            lock_head=$(head -n 1 "$lock_file" 2>/dev/null)
            lock_tail=$(tail -n 1 "$lock_file" 2>/dev/null)
            [[ $lock_head = $module-$module_version && $time_stamp && $lock_tail = "$time_stamp" ]] || continue
            rm -f "${lock_file:?}"
            die 0 "Remove cancelled because --rpm_safe_upgrade scenario detected."
        done
    fi

    local i
    for ((i=0; i < ${#kernelver[@]}; i++)); do
        maybe_uninstall_module "${kernelver[$i]}" "${arch[$i]}"
        maybe_unbuild_module "${kernelver[$i]}" "${arch[$i]}"
        echo ""
    done

    # Delete the $module_version part of the tree if no other $module_version/$kernel_version dirs exist
    if ! find "$dkms_tree/$module/$module_version/"* -maxdepth 0 -type d 2>/dev/null | grep -Eqv "(build|tarball|driver_disk|rpm|deb|source)$"; then
        echo "Deleting module $module/$module_version completely from the DKMS tree."
        rm -rf "${dkms_tree:?}/$module/$module_version"
    fi

    # Get rid of any remnant directories if necessary
    # shellcheck disable=SC2012
    # TODO: use find instead
    if (($(ls "$dkms_tree/$module" | wc -w | awk '{print $1}') == 0)); then
        rm -rf "${dkms_tree:?}/$module" 2>/dev/null
    fi
}

# Given a kernel object, figure out which DKMS module it is from.
find_module_from_ko()
{
    local ko
    local basename_ko
    ko="$1"
    basename_ko="${ko##*/}"

    local module
    local kernellink

    for kernellink in "$dkms_tree"/*/kernel-*; do
        [[ -L $kernellink ]] || continue
        module=${kernellink#"${dkms_tree}/"}
        module=${module%/kernel-*}
        diff "$kernellink/module/${basename_ko}" "${ko}" >/dev/null 2>&1 || continue
        rest=$(readlink "$kernellink")
        echo "$module/$rest"
        return 0
    done
    return 1
}

# Check to see if modules meeting the passed parameters are weak-installed.
# This function's calling convention is different from the usual DKMS status
# checking functions -- the kernel version we usually have is the one we are currently
# running on, not necessarily the one we compiled the module for.
module_status_weak() {
    # $1 = module, $2 = module version, $3 = kernel version weak installed to,
    # $4 = kernel arch, $5 = kernel version built for
    [[ $NO_WEAK_MODULES ]] || return 1
    [[ $weak_modules_add ]] && [[ $weak_modules_remove ]] || return 1
    local m
    local v
    local k
    local a
    local kern
    local weak_ko
    local mod
    local installed_ko
    local f
    local ret
    local oifs
    ret=1
    oifs=$IFS

    local -A already_found
    for weak_ko in "$install_tree/"*/weak-updates/*; do
        [[ -e $weak_ko ]] || continue
        if [[ -L $weak_ko ]]; then
            installed_ko="$(readlink -f "$weak_ko")"
        else
            continue
        fi
        IFS=/ read -r m v k a <<< "$(IFS=$oifs find_module_from_ko "$weak_ko")"
        [[ $m ]] || continue
        kern=${weak_ko#"${install_tree}/"}
        kern=${kern%/weak-updates/*}
        [[ $m = "${1:-*}" && $v = "${2:-*}" && $k = "${5:-*}" && $a = "${4:-*}" && $kern = "${3:-*}" ]] || continue
        already_found[$m/$v/$kern/$a/$k]+=${weak_ko##*/}" "
    done
    # Check to see that all ko's are present for each module
    for mod in "${!already_found[@]}"; do
        IFS=/ read -r m v k a kern <<< "$mod"
        # ensure each module is weak linked
        # shellcheck disable=SC2044
        # TODO: use find + -exec instead
        for installed_ko in $(find "$dkms_tree/$m/$v/$kern/$a/module" -type f); do
            [[ ${already_found[$mod]} != *"$installed_ko"* ]] && continue 2
        done
        ret=0
        echo "installed-weak $mod"
    done
    return $ret
}

# Print the requested status lines for weak-installed modules.
do_status_weak()
{
    local mvka
    local m
    local v
    local k
    local a
    local kern
    local status
    while read -r status mvka; do
        [[ $status ]] || continue
        IFS=/ read -r m v k a kern <<< "$mvka"
        echo "$m, $v, $k, $a: installed-weak from $kern"
    done <<< "$(module_status_weak "$@")"
}

# Spit out all the extra status information that people running DKMS are
# interested in, but that the DKMS internals do not usually care about.
module_status_built_extra() (
    set_module_suffix "$3"
    read_conf "$3" "$4" "$dkms_tree/$1/$2/source/dkms.conf"
    [[ -d $dkms_tree/$1/original_module/$3/$4 ]] && echo -n " (Original modules exist)"
    for ((count=0; count < num_modules; count++)); do
        # Skip modules that are excluded due to BUILD_EXCLUSIVE restrictions
        if ! check_build_exclusive_for_module "$count" "$3" "$4"; then
            continue
        fi

        tree_mod=$(compressed_or_uncompressed "$dkms_tree/$1/$2/$3/$4/module" "${dest_module_name[$count]}")
        if [[ ! $tree_mod ]]; then
            echo -n " (Built modules are missing in the kernel modules folder)"
            break
        elif _is_module_installed "$@"; then
            real_dest="$(find_actual_dest_module_location "$1" $count "$3" "$4")"
            real_dest_mod=$(compressed_or_uncompressed "$install_tree/$3${real_dest}" "${dest_module_name[$count]}")
            if ! diff -q "$tree_mod" "$real_dest_mod" >/dev/null 2>&1; then
                echo -n " (Differences between built and installed modules)"
                break
            fi
        fi
    done
)

# Return a list of all the modules that are either built or installed.
# This and list_module_version_combos do some juggling of $IFS to ensure that
# we do not get word splitting where it would be inconvenient.
module_status_built() {
    local ret
    local directory
    local ka
    local k
    local a
    local state
    local oifs
    ret=1
    oifs="$IFS"
    IFS=''
    for directory in "$dkms_tree/$1/$2/"${3:-+([0-9]).*}/${4:-*}; do
        IFS="$oifs"
        ka="${directory#"${dkms_tree}/${1}/${2}/"}"
        k="${ka%/*}"
        a="${ka#*/}"
        is_module_built "$1" "$2" "$k" "$a" || continue
        ret=0
        state="built"
        _is_module_installed "$1" "$2" "$k" "$a" && state="installed"
        echo "$state $1/$2/$k/$a"
        IFS=''
    done
    IFS="$oifs"
    return $ret
}

# Return a list of all module/version combos known to DKMS.
list_module_version_combos() {
    local ret
    local modv
    local directory
    local oifs
    ret=1
    oifs="$IFS"
    IFS=''
    for directory in "$dkms_tree/"${1:-*}/${2:-*}; do
        IFS="$oifs"
        [[ -d $directory ]] || continue
        modv="${directory#"${dkms_tree}/"}"
        # skip <module>/kernel-<kver>-<karch> -> <modver>/<kver>/<karch> symlinks
        [[ $modv != */kernel-*-* ]] || continue
        echo "$modv"
        ret=0
        IFS=''
    done
    IFS="$oifs"
    return $ret
}

# Return the status of all modules that have been added, built, or installed.
module_status() {
    local ret
    local m
    local v
    ret=1
    while IFS='/' read -r m v; do
        [[ $m ]] || continue
        is_module_broken "$m" "$v" && { echo "broken $m/$v"; continue; }
        is_module_added "$m" "$v" || continue
        ret=0
        module_status_built "$m" "$v" "$3" "$4" || echo "added $m/$v"
    done <<< "$(list_module_version_combos "$1" "$2")"
    return $ret
}

# Print out the status in the format that people who call DKMS expect.
# Internal callers should use the module_status functions, as their output
# is easier to parse.
do_status() {
    local status
    local mvka
    local m
    local v
    local k
    local a
    while read -r status mvka; do
        IFS=/ read -r m v k a <<< "$mvka"
        case $status in
            broken)
                echo "$m/$v: $status"
                error "$m/$v: Missing the module source directory or the symbolic link pointing to it."\
                    "Manual intervention is required!"
                ;;
            added)
                echo "$m/$v: $status"
                ;;
            built|installed)
                echo -n "$m/$v, $k, $a: $status"
                module_status_built_extra "$m" "$v" "$k" "$a"
                echo
                ;;
        esac
    done <<< "$(module_status "$@")"
}

# Show all our status in the format that external callers expect, even
# though it is slightly harder to parse.
show_status()
{
    local j
    if ((${#kernelver[@]} == 0)); then
        do_status "$module" "$module_version" "$kernelver" "$arch"
        do_status_weak "$module" "$module_version" "$kernelver" "$arch"
    else
    for ((j=0; j < ${#kernelver[@]}; j++)); do
        do_status "$module" "$module_version" "${kernelver[$j]}" "${arch[$j]}"
        do_status_weak "$module" "$module_version" "${kernelver[$j]}" "${arch[$j]}"
    done
    fi
}

make_tarball()
{
    # Read the conf file
    read_conf_or_die "$kernelver" "$arch"

    local -r temp_dir_name=$(mktemp_or_die -d "$tmp_location/dkms.XXXXXX")
    make_tarball_rm_temp_dir_name="rm -rf ${temp_dir_name:?}"
    mkdir -p "$temp_dir_name/dkms_main_tree"

    if [[ $source_only ]]; then
    kernel_version_list="source-only"
    else
    local i
    for ((i=0; i<${#kernelver[@]}; i++)); do
        local intree_module_dir="$dkms_tree/$module/$module_version/${kernelver[$i]}/${arch[$i]}"
        local temp_module_dir="$temp_dir_name/dkms_main_tree/${kernelver[$i]}"

        if ! [[ -d "$intree_module_dir" ]]; then
            die 6 "No modules built for ${kernelver[$i]} (${arch[$i]})." \
                "Modules must already be in the built state before using mktarball."
        fi

        set_module_suffix "${kernelver[$i]}"

        echo "Marking modules for ${kernelver[$i]} (${arch[$i]}) for archiving..."
        if [[ ! $kernel_version_list ]]; then
            kernel_version_list="kernel${kernelver[$i]}-${arch[$i]}"
        else
            kernel_version_list="${kernel_version_list}-kernel${kernelver[$i]}-${arch[$i]}"
        fi
        mkdir -p "$temp_module_dir"
        CP -rf "$intree_module_dir" "$temp_module_dir"
    done
    fi

    local -r source_dir="$dkms_tree/$module/$module_version/source"

    # Copy the source_tree or make special binaries-only structure
    if [[ $binaries_only ]]; then
        local -r binary_only_dir="$temp_dir_name/dkms_binaries_only"

        echo ""
        echo "Creating tarball structure to specifically accomodate binaries."

        mkdir "$binary_only_dir"
        echo "$module" > "$binary_only_dir/PACKAGE_NAME"
        echo "$module_version" > "$binary_only_dir/PACKAGE_VERSION"
        [[ ! $conf ]] && conf="$source_dir/dkms.conf"
        CP -f "$conf" "$binary_only_dir/" 2>/dev/null
    else
        echo ""
        echo "Marking $source_dir for archiving..."
        mkdir -p "$temp_dir_name/dkms_source_tree"
        CP -fprT "$source_dir/" "$temp_dir_name/dkms_source_tree"
    fi

    # shellcheck disable=SC1083
    if (( $(echo "$kernel_version_list" | wc -m | awk {'print $1'}) > 200 )); then
        kernel_version_list="manykernels"
    fi

    local tarball_name
    local tarball_dest
    tarball_name="$module-$module_version-$kernel_version_list.dkms.tar.gz"
    tarball_dest="$dkms_tree/$module/$module_version/tarball/"

    if [[ $archive_location ]]; then
        tarball_name="${archive_location##*/}"
        if [[ ${archive_location%/*} != "$archive_location" ]]; then
            tarball_dest="${archive_location%/*}"
        fi
    fi

    echo ""
    echo "Tarball location: $tarball_dest/$tarball_name"

    if [[ ! -d $tarball_dest ]]; then
        if ! mkdir -p "$tarball_dest" 2>/dev/null; then
            die 9 "Missing write permissions for $tarball_dest."
        fi
    fi

    [[ -w $tarball_dest ]] || die 9 "Missing write permissions for $tarball_dest."

    if ! tar -C "$temp_dir_name" -caf "$tarball_dest/$tarball_name" . 2>/dev/null; then
        die 6 "Failed to make tarball."
    fi

    eval "$make_tarball_rm_temp_dir_name"
    unset make_tarball_rm_temp_dir_name
}

# A tiny helper function to make sure dkms.conf describes a valid package.
get_pkginfo_from_conf() {
    [[ -f $1 && $1 = *dkms.conf ]] || return
    read_conf_or_die "$kernelver" "$arch" "$1"
    [[ $PACKAGE_NAME && $PACKAGE_VERSION ]]
}

# Unpack a DKMS tarball from a few different supported formats.
# We expect $archive_location to have been passed either as a raw argument or
# with --archive.
load_tarball()
{
    # Error out if $archive_location does not exist
    if [[ ! -e $archive_location ]]; then
        die 2 "$archive_location does not exist."
    fi

    # If it is an .rpm file. install it with rpm, run an autoinstall, and then exit.
    if [[ $archive_location = *.rpm ]]; then
       if rpm -Uvh "$archive_location"; then
           autoinstall
           exit $?
       else
           die 9 "Unable to install $archive_location using rpm." \
               "Check to ensure that your system can install .rpm files."
       fi
    fi

    # Untar it into $tmp_location
    local -r temp_dir_name=$(mktemp_or_die -d "$tmp_location/dkms.XXXXXX")
    load_tarball_rm_temp_dir_name="rm -rf ${temp_dir_name:?}"
    tar -xaf "$archive_location" -C "$temp_dir_name"

    if [[ ! -d $temp_dir_name/dkms_main_tree ]]; then
    # Tarball was not generated from mktarball.
    # Just find the dkms.conf file and load the source.
    conf=$(find "$temp_dir_name/" -name dkms.conf 2>/dev/null | head -n 1)
    if [[ ! $conf ]]; then
        die 3 "Tarball does not appear to be a correctly formed DKMS archive. No dkms.conf found within it."
    fi
    add_source_tree "${conf%dkms.conf}"
    return
    fi

    # Make sure its a sane tarball. Sane ones will have one of the two
    # directories we test for.
    for loc in dkms_source_tree dkms_binaries_only ''; do
        if [[ ! $loc ]]; then
            die 7 "No valid dkms.conf in dkms_source_tree or dkms_binaries_only." \
                "$archive_location is not a valid DKMS tarball."
        fi
        local conf
        conf="$temp_dir_name/$loc/dkms.conf"
        [[ -f $conf ]] || continue
        if ! get_pkginfo_from_conf "$conf"; then
            echo >&2
            echo "Malformed dkms.conf, refusing to load." >&2
            continue
        fi
        if is_module_added "$PACKAGE_NAME" "$PACKAGE_VERSION" && \
            [[ ! $force ]]; then
            die 8 "$PACKAGE_NAME/$PACKAGE_VERSION is already added!" \
            "Aborting."
        fi
        # Success!
        break
    done

    module="$PACKAGE_NAME"; module_version="$PACKAGE_VERSION"
    echo ""
    echo "Loading tarball for $module/$module_version"
    case $loc in
        dkms_source_tree)
            add_source_tree "$temp_dir_name/dkms_source_tree"
            ;;
        dkms_binaries_only)
            #if there is a source tree on the system already, don't build a binaries stub
            if [[ ! -d $source_tree/$module-$module_version ]]; then
                local -r source_dir="$dkms_tree/$module/$module_version/source"

                echo "Creating $source_dir"
                mkdir -p "$source_dir"
                echo "Copying dkms.conf to $source_dir ..."
                CP -rf "$temp_dir_name/dkms_binaries_only/dkms.conf" "$source_dir"
            fi
            ;;
    esac

    # At this point, the source has been copied to the appropriate location
    # and registered with dkms, or a binary-only config has been noted.
    # Now, add any included precompiled modules.

    # Load precompiled modules.
    for directory in "$temp_dir_name/dkms_main_tree"/*/*; do
        [[ -d $directory ]] || continue

        local -r kernel_arch_to_load=${directory/*dkms_main_tree\/}
        local -r dkms_dir_location=$dkms_tree/$module/$module_version/$kernel_arch_to_load

        if [[ -d $dkms_dir_location && ! $force ]]; then
            warn "$dkms_dir_location already exists. Skipping..."
        else
            echo "Loading $dkms_dir_location..."
            rm -rf "${dkms_dir_location:?}"
            mkdir -p "$dkms_dir_location"
            CP -rf "$directory/"* "$dkms_dir_location/"
        fi
    done

    eval "$load_tarball_rm_temp_dir_name"
    unset load_tarball_rm_temp_dir_name

    [[ $loc != dkms_binaries_only ]] || [[ -d $source_tree/$module-$module_version ]]
}

run_match()
{
    set_kernel_source_dir_and_kconfig "$kernelver"

    # Error if $template_kernel is unset
    if [[ ! $template_kernel ]]; then
        die 1 "Invalid number of parameters passed." \
            "Usage: match --templatekernel=<kernel-version> -k <kernel-version>" \
            "   or: match --templatekernel=<kernel-version> -k <kernel-version> <module>"
    fi

    # Error out if $template_kernel = $kernel_version
    if [[ $template_kernel = "$kernelver" ]]; then
        die 2 "The templatekernel and the specified kernel version are the same."
    fi

    # Read in the status of template_kernel
    local template_kernel_status
    template_kernel_status=$(do_status '' '' "$template_kernel" "$arch" | grep ": installed")

    # If $module is set, grep the status only for that module
    if [[ $module ]]; then
        # Make sure that its installed in the first place
        if ! [[ -d $dkms_tree/$module/ ]]; then
            die 3 "The module: $module is not located in the DKMS tree."
        fi
        template_kernel_status=$(echo "$template_kernel_status" | grep "^$module,")
    fi

    echo ""
    echo "Matching modules in kernel: $kernelver ($arch)"
    echo "to the configuration of kernel: $template_kernel ($arch)"

    # Prepare the kernel just once but only if there is actual work to do
    if [[ ! $template_kernel_status ]]; then
        echo ""
        echo "There is nothing to be done for this match."
        return 0
    fi

    prepare_kernel "$kernelver"

    # Iterate over the kernel_status and match kernel to the template_kernel
    while read -r template_line; do
        # shellcheck disable=SC1083
        template_module=$(echo "$template_line" | awk {'print $1'} | sed 's/,$//')
        # shellcheck disable=SC1083
        template_version=$(echo "$template_line" | awk {'print $2'} | sed 's/,$//')

        # Print out a match header
        echo "Module:  $template_module"
        echo "Version: $template_version"

        # Continue if the status is broken, as there is nothing we can do
        if is_module_broken "$template_module" "$template_version"; then
            error "$template_module/$template_version is broken!"\
                "Missing the source directory or the symbolic link pointing to it."\
                "Manual intervention is required!"
            continue
        fi
        maybe_build_module "$template_module" "$template_version" "$kernelver" "$arch"
        maybe_install_module "$template_module" "$template_version" "$kernelver" "$arch"
    done <<< "$template_kernel_status"
}

report_build_problem()
{
    # If apport is on the system, files a build problem
    if [[ -x /usr/share/apport/apport ]] && command -v python3 >/dev/null; then
        python3 /usr/share/apport/package-hooks/dkms_packages.py -m "$module" -v "$module_version" -k "${kernelver[0]}"
    fi
    die "$@"
}

# Little helper function for reading args from the commandline.
# It automatically handles -a b and -a=b variants, and returns 1 if
# we need to shift $3.
read_arg() {
    # $1 = arg name
    # $2 = arg value
    # $3 = arg parameter
    local rematch
    rematch='^[^=]*=(.*)$'
    if [[ $2 =~ $rematch ]]; then
        read -r "$1" <<< "${BASH_REMATCH[1]}"
    else
        read -r "$1" <<< "$3"
        # There is no way to shift our callers args, so
        # return 1 to indicate they should do it instead.
        return 1
    fi
}

# A couple of helper functions for parsing out our most common arguments.
# This one allows you to pass -k kernel.version-extra/arch instead of
# -k kernel-version.extra -a arch.
# This makes it harder to pass mismatching numbers of kernel/arch pairs, because
# they are all passed at the same time.
parse_kernelarch(){
    if [[ $1 =~ $mv_re ]]; then
        kernelver[${#kernelver[@]}]="${BASH_REMATCH[1]}"
        arch[${#arch[@]}]="${BASH_REMATCH[2]}"
    else
        kernelver[${#kernelver[@]}]="$1"
    fi
}

# This allows you to pass module and module_version information on the commandline
# in a more convenient form.  Instead of the mostly mandatory and annoying
# -m module -v module_version, you can use either -m module/module_version,
# or just a raw module/module_version with no -m parameter.
# This vastly improves readability and discoverability of
# commands on the commandline.
parse_moduleversion(){
    if [[ $1 =~ $mv_re ]]; then
        module="${BASH_REMATCH[1]}"
        module_version="${BASH_REMATCH[2]}"
    else
        module="$1"
    fi
}

check_root() {
    [[ $(id -u) = 0 ]] && return
    die 1 "You must be root to use this command."
}

check_rw_dkms_tree() {
    [[ -w "$dkms_tree" ]] && return
    die 1 "No write access to DKMS tree at ${dkms_tree}"
}

# Add a passed source tree to the default source location.
# We will check the dkms.conf file to make sure it is valid
# beforehand.
add_source_tree() {
    local from
    from=$(readlink -f "$1")
    if ! [[ $from && -f $from/dkms.conf ]]; then
        die 9 "$1 must contain a dkms.conf file!"
    fi
    check_root
    setup_kernels_arches
    if ! get_pkginfo_from_conf "$from/dkms.conf" ; then
        die 10 "Malformed dkms.conf file. Cannot load source tree."
    fi
    module="$PACKAGE_NAME"
    module_version="$PACKAGE_VERSION"
    if [[ $force && -d $source_tree/$module-$module_version ]]; then
        echo >&2
        echo "Forcing install of $module/$module_version"
        rm -rf "${source_tree:?}/$module-$module_version"
    fi

    # We are already installed, just return.
    case $from in
        "$source_tree/$module-$module_version")
            return
            ;;
        "$dkms_tree/$module/$module_version/source")
            return
            ;;
        "$dkms_tree/$module/$module_version/build")
            return
            ;;
    esac
    mkdir -p "$source_tree/$module-$module_version"
    CP -fprT "$from/" "$source_tree/$module-$module_version"
}

# This code used to be in dkms_autoinstaller.
# Moving it into the main dkms script gets rid of a fair amount of duplicate
# functionality, and makes it much easier to reinstall DKMS kernel modules
# by hand if dkms_autoinstaller is not used.
autoinstall() {
    if [[ -f /etc/dkms/no-autoinstall ]]; then
        echo "Automatic installation of modules has been disabled."
        return
    fi

    local status
    local mv
    local mvka
    local m
    local v
    local k
    local a
    local progress
    local next_depends
    local -a to_install
    local -a next_install
    local -a known_modules
    local -a installed_modules
    local -a skipped_modules
    local -a failed_modules
    local -A build_depends
    local -A latest

    # Walk through our list of installed and built modules, and create
    # a list of modules and their latest version.
    while read -r status mvka; do
        [[ $status ]] || continue
        IFS='/' read -r m v k a <<< "$mvka"
        # If the module status is broken there is nothing that can be done
        if [[ $status = broken ]]; then
            error "$m/$v is broken! Missing the source directory or the symbolic link pointing to it."\
                "Manual intervention is required!"
            continue
        fi
        if [[ ! ${latest[$m]} ]]; then
            known_modules[${#known_modules[@]}]="$m"
            latest[$m]="$v"
        elif [[ ("$(VER "$v")" > "$(VER "${latest["$m"]}")") ]]; then
            latest["$m"]="$v"
        fi
    done <<< "$(module_status)"

    # Walk through our list of known modules, and create
    # a list of modules that need to be reinstalled.
    for m in "${known_modules[@]}"; do
        v="${latest["$m"]}"
        # If the module is already installed or weak-installed, skip it.
        if _is_module_installed "$m" "$v" "${kernelver[0]}" "${arch[0]}"; then
            installed_modules[${#installed_modules[@]}]="$m"
            continue
        fi
        if module_status_weak "$m" "$v" "${kernelver[0]}" "${arch[0]}" >/dev/null; then
            installed_modules[${#installed_modules[@]}]="$m"
            continue
        fi
        # If the module does not want to be autoinstalled, skip it.
        module=$m module_version=$v read_conf_or_die "${kernelver[0]}" "${arch[0]}" "$dkms_tree/$m/$v/source/dkms.conf"
        if [[ ! $AUTOINSTALL ]]; then
            continue
        fi
        # Otherwise, autoinstall the latest version we have hanging around.
        to_install[${#to_install[@]}]="$m/$v"
        build_depends["$m"]="${BUILD_DEPENDS[*]}"
    done

    [[ ${#to_install[@]} -eq 0 ]] && return 0

    while true; do
        progress=0
        next_install=( )

        # Step 1: Remove installed modules from all dependency lists.
        for m in "${!build_depends[@]}"; do
            next_depends=
            for d in ${build_depends[$m]}; do
                for i in "${installed_modules[@]}" "${skipped_modules[@]}"; do
                    [[ "$d" = "$i" ]] && continue 2
                done
                next_depends+="$d "
            done
            build_depends[$m]="${next_depends%% }"
        done

        # Step 2: Install modules that have an empty dependency list.
        for modv in "${to_install[@]}"; do
            IFS=/ read -r m v <<< "$modv"
            if [[ ! ${build_depends[$m]} ]]; then
                is_module_built "$m" "$v" "${kernelver[0]}" "${arch[0]}" || prepare_kernel_and_signing
                echo "Autoinstall of module $m/$v for kernel ${kernelver[0]} (${arch[0]})"
                (module="$m" module_version="$v" kernelver="${kernelver[0]}" arch="${arch[0]}" install_module)
                status=$?
                if (( status == 0 )); then
                    installed_modules[${#installed_modules[@]}]="$m"
                    progress=$((progress +1))
                elif (( status == 77 )); then
                    skipped_modules[${#skipped_modules[@]}]="$m"
                    progress=$((progress +1))
                else
                    failed_modules[${#failed_modules[@]}]="$m($status)"
                fi
                echo ""
            else
                next_install[${#next_install[@]}]="$modv"
            fi
        done

        wait

        # Step 3: Remove modules that install was attempted for
        # during Step 2 from the job queue.
        to_install=( "${next_install[@]}" )

        # Step 4: Keep going if at least one module was installed during
        # this iteration.
        (( progress > 0 )) || break;

    done

    if (( ${#installed_modules[@]} > 0 )); then
        echo "Autoinstall on ${kernelver[0]} succeeded for module(s) ${installed_modules[*]}."
    fi

    if (( ${#skipped_modules[@]} > 0 )); then
        echo "Autoinstall on ${kernelver[0]} was skipped for module(s) ${skipped_modules[*]}."
    fi

    if (( ${#failed_modules[@]} > 0 )); then
        echo "Autoinstall on ${kernelver[0]} failed for module(s) ${failed_modules[*]}."
    fi

    for mv in "${to_install[@]}"; do
        IFS=/ read -r m v <<< "$mv"
        echo "$m/$v autoinstall failed due to missing dependencies: ${build_depends[$m]}."
    done

    if (( ${#failed_modules[@]} > 0 || ${#to_install[@]} > 0 )); then
        die 11 "One or more modules failed to install during autoinstall." \
            "Refer to previous errors for more information."
    fi
}

# This is roughly the inverse action to 'autoinstall'. It is supposed to be
# called before upgrade of a kernel to first remove all modules that are
# currently built or installed for that kernel to ensure they get rebuilt
# by kernel_postinst later on.
# Upon initial installation of a kernel this is a no-op.
#
# Ideally we should only mark them as needing a rebuild instead of removing
# them right away, but such functionality is yet to be implemented.
kernel_preinst()
{
    local m
    local v
    local failed

    have_one_kernel kernel_prerm

    # run depmod only once after uninstalling all dkms modules
    delayed_depmod=1

    while IFS='/' read -r m v; do
        is_module_built "$m" "$v" "${kernelver[0]}" "${arch[0]}" || continue
        read_conf_or_die "${kernelver[0]}" "${arch[0]}" "$dkms_tree/$m/$v/source/dkms.conf"
        [[ $AUTOINSTALL ]] || continue
        echo "dkms: removing module $m/$v for kernel ${kernelver[0]} (${arch[0]})" >&2
        (module="$m" module_version="$v" unbuild_module) || failed="$failed $m/$v($?)"
    done <<< "$(list_module_version_combos)"

    if [[ -f $dkms_tree/depmod-pending-${kernelver[0]}-${arch[0]} ]]; then
        rm -f "${dkms_tree:?}/depmod-pending-${kernelver[0]}-${arch[0]}"
        invoke_command "do_depmod $1" "Running depmod" '' background
    fi
    delayed_depmod=

    # clean leftover empty directories
    [[ ! -d $install_tree/${kernelver[0]} ]] || find "$install_tree/${kernelver[0]}" -type d -empty -delete

    [[ ! $failed ]] || die 14 "dkms kernel_preinst for kernel ${kernelver[0]} (${arch[0]}) failed for module(s)$failed."
}

# A wrapper for 'autoinstall', to be used in combination with 'kernel_prerm'.
kernel_postinst()
{
    local m
    local v
    local failed

    have_one_kernel kernel_postinst

    autoinstall
}

# This is roughly the inverse action to 'autoinstall'. It is supposed to be
# called upon removal of a kernel to also remove all modules (including
# those with AUTOINSTALL="") that were built or installed for that kernel.
#
# Compromise on using 'unbuild' to remove the module when a kernel is being
# removed. The 'remove' command is too destructive. The 'uninstall' command
# leaves built files around that have no other trigger to 'unbuild' them.
# (Triggering 'unbuild' on kernel header removal would not be a good idea
# because that would also cause the module to be uninstalled for the kernel,
# even though only the headers are being removed.)
kernel_prerm()
{
    local m
    local v
    local failed

    have_one_kernel kernel_prerm

    # run depmod only once after uninstalling all dkms modules
    delayed_depmod=1

    while IFS='/' read -r m v; do
        [[ $m ]] || continue
        is_module_built "$m" "$v" "${kernelver[0]}" "${arch[0]}" || continue
        echo "dkms: removing module $m/$v for kernel ${kernelver[0]} (${arch[0]})" >&2
        (module="$m" module_version="$v" unbuild_module) || failed="$failed $m/$v($?)"
        echo ""
    done <<< "$(list_module_version_combos)"

    if [[ -f $dkms_tree/depmod-pending-${kernelver[0]}-${arch[0]} ]]; then
        rm -f "${dkms_tree:?}/depmod-pending-${kernelver[0]}-${arch[0]}"
        invoke_command "do_depmod $1" "Running depmod" '' background
    fi
    delayed_depmod=

    # clean leftover empty directories
    [[ ! -d $install_tree/${kernelver[0]} ]] || find "$install_tree/${kernelver[0]}" -type d -empty -delete

    [[ ! $failed ]] || die 14 "dkms kernel_prerm for kernel ${kernelver[0]} (${arch[0]}) failed for module(s)$failed."
}

# Check if a module's dependencies have been updated
check_dependencies_updated() {

    local module=$1
    local module_version=$2

    # $1 = module
    # $2 = module version
    # $3 = kernel version
    # $4 = arch

    local deps_updated=0

    # Read the module's configuration
    set_module_suffix "$3"
    read_conf_strict_or_die "$3" "$4"

    # Check each dependency
    for bd in "${BUILD_DEPENDS[@]}"; do
        # Get the latest version of the dependency
        local dep_version
        dep_version=$(module_status "$bd" "*" "$3" "$4" | while read -r status mvka; do
            [[ $status ]] || continue
            IFS='/' read -r m v k a <<< "$mvka"
            echo "$v"
        done | sort -V | tail -n1)

        # If dependency is not installed, skip it
        [[ $dep_version ]] || continue

        # Check if dependency's version has changed since last build
        local dep_version_file="$dkms_tree/$module/$module_version/$3/$4/.dep_${bd}"
        if [[ -f "$dep_version_file" ]]; then
            local old_version
            old_version=$(cat "$dep_version_file")
            if [[ "$old_version" != "$dep_version" ]]; then
                deps_updated=1
                break
            fi
        else
            deps_updated=1
            break
        fi
    done

    return $deps_updated
}

# Check and rebuild all modules with updated dependencies
check_and_rebuild_dependent_modules() {

    # $1 = kernel version
    # $2 = arch

    local -a modules_to_rebuild
    local -a rebuilt_modules
    local progress=1

    # First pass: collect all modules that need rebuilding
    while read -r status mvka; do
        [[ $status ]] || continue
        IFS='/' read -r m v k a <<< "$mvka"
        # Skip if not built for this kernel/arch
        [[ "$k" = "$1" && "$a" = "$2" ]] || continue

        # Read module's configuration by passing module and version of the dependent module)
        module=$m module_version=$v read_conf_or_die "$1" "$2"

        # Skip if no BUILD_DEPENDS, BUILD_DEPENDS_REBUILD is not set or --force is passed
        [[ ${#BUILD_DEPENDS[@]} -eq 0 ]] || [[ -z $BUILD_DEPENDS_REBUILD ]] || [[ $force ]] && continue

        # Check if dependencies have been updated
        if ! check_dependencies_updated "$m" "$v" "$1" "$2"; then
            modules_to_rebuild+=("$m/$v")
        fi
    done <<< "$(module_status)"

    # Second pass: rebuild modules in dependency order
    while (( progress > 0 )); do
        progress=0
        local -a next_rebuild

        # Remove already rebuilt modules from dependency lists
        for mv in "${modules_to_rebuild[@]}"; do
            IFS=/ read -r m v <<< "$mv"
            module=$m module_version=$v read_conf_or_die "$1" "$2"

            # Check if all dependencies are satisfied
            # shellcheck disable=SC2034
            local can_rebuild=1
            for bd in "${BUILD_DEPENDS[@]}"; do
                local dep_found=0
                for rm in "${rebuilt_modules[@]}"; do
                    IFS=/ read -r dm <<< "$rm"
                    [[ "$dm" = "$bd" ]] && dep_found=1 && break
                done
                if (( ! dep_found )); then
                    can_rebuild=0
                    break
                fi
            done

            if (( ! can_rebuild )); then
                module=$m module_version=$v
                echo ""
                echo "Rebuilding module $m/$v due to updated dependencies"
                echo ""
                do_uninstall "$1" "$2"
                do_unbuild "$1" "$2"
                if do_build; then
                    do_install
                    rebuilt_modules+=("$m/$v")
                    progress=1
                fi
            else
                next_rebuild+=("$m/$v")
            fi
        done
        modules_to_rebuild=("${next_rebuild[@]}")
    done

    # Report any modules that couldn't be rebuilt
    if (( ${#modules_to_rebuild[@]} > 0 )); then
        echo "Warning: The following modules could not be rebuilt due to dependency cycles:"
        for mv in "${modules_to_rebuild[@]}"; do
            echo "  $mv"
        done
    fi
}

#############################
####                     ####
#### Program Starts Here ####
####                     ####
#############################

# Ensure files and directories we create are readable to anyone,
# since we aim to build as a non-root user
umask 022

# Unset environment variables that may interfere with the build
unset CC CXX CFLAGS CXXFLAGS LDFLAGS

# Set important variables
# shellcheck disable=SC2034
# not used now, might use later
current_kernel=$(uname -r)
current_os=$(uname -s)
running_distribution=$(distro_version) || exit
dkms_tree="/var/lib/dkms"
source_tree="/usr/src"
install_tree="@MODDIR@"
tmp_location=${TMPDIR:-/tmp}
verbose=""
symlink_modules=""

# Set compression defaults
compress_gzip_opts=""
compress_xz_opts="--check=crc32 --lzma2=dict=1MiB"
compress_zstd_opts="-q --rm -T0"

# Check that we can write temporary files
tmpfile=$(mktemp_or_die)
echo "Hello, DKMS!" > "$tmpfile"
if [[ "$(cat "$tmpfile")" != "Hello, DKMS!" ]]; then
    warn "dkms will not function properly without some free space in \$TMPDIR ($tmp_location)."
fi
rm -f "${tmpfile:?}"

# These can come from the environment or the config file
# shellcheck disable=SC1091
[[ ! ${ADDON_MODULES_DIR} && -e /etc/sysconfig/module-init-tools ]] && . /etc/sysconfig/module-init-tools
addon_modules_dir="${ADDON_MODULES_DIR}"

# Source in configuration not related to signing
read_framework_conf "$dkms_framework_nonsigning_variables"

# Clear out command line argument variables
module=""
module_version=""
template_kernel=""
conf=""
kernel_config=""
kconfig_fromcli=""
archive_location=""
kernel_source_dir=""
ksourcedir_fromcli=""
action=""
force=""
force_version_override=""
binaries_only=""
source_only=""
all=""
module_suffix=""
module_uncompressed_suffix=""
module_compressed_suffix=""
rpm_safe_upgrade=""
declare -a directive_array=() kernelver=() arch=()
weak_modules_add=''
weak_modules_remove=''
last_mvka=''
last_mvka_conf=''
try_source_tree=''
die_is_fatal="yes"
no_depmod=""
delayed_depmod=""
prepared_kernel="none"

action_re='^(remove|(auto|un)?install|match|mktarball|(un)?build|add|status|ldtarball|generate_mok|kernel_(preinst|postinst|prerm))$'

# Parse command line arguments
while (($# > 0)); do
    case $1 in
        --module*|-m)
            read_arg _mv "$1" "$2" || shift
            # shellcheck disable=SC2154
            parse_moduleversion "$_mv"
            ;;
        -v)
            read_arg module_version "$1" "$2" || shift
            ;;
        --kernelver*|-k)
            read_arg _ka "$1" "$2" || shift
            # shellcheck disable=SC2154
            parse_kernelarch "$_ka"
            ;;
        --templatekernel*)
            read_arg template_kernel "$1" "$2" || shift
            ;;
        -c)
            read_arg conf "$1" "$2" || shift
            ;;
        --quiet|-q)
            exec >/dev/null 2>&1
            ;;
        --version|-V)
            echo "@RELEASE_STRING@"
            exit 0
            ;;
        --no-initrd)
            # This is an old option, consume and warn
            deprecated "--no-initrd"
            ;;
        --no-clean-kernel)
            # This is an old option, consume and warn
            deprecated "--no-clean-kernel"
            ;;
        --no-prepare-kernel)
            # This is an old option, consume and warn
            deprecated "--no-prepare-kernel"
            ;;
        --binaries-only)
            binaries_only="binaries-only"
            ;;
        --source-only)
            source_only="source-only"
            ;;
        --force)
            force="true"
            ;;
        --force-version-override)
            force_version_override="true"
            ;;
        --all)
            all="true"
            ;;
        --verbose)
            verbose="true"
            ;;
        --rpm_safe_upgrade)
            rpm_safe_upgrade="true"
            ;;
        --dkmstree*)
            read_arg dkms_tree "$1" "$2" || shift
            ;;
        --sourcetree*)
            read_arg source_tree "$1" "$2" || shift
            ;;
        --installtree*)
            read_arg install_tree "$1" "$2" || shift
            ;;
        --symlink-modules)
            symlink_modules="true"
            ;;
        --config*)
            read_arg kernel_config "$1" "$2" || shift
            kconfig_fromcli="true"
            ;;
        --archive*)
            read_arg archive_location "$1" "$2" || shift
            ;;
        --arch*|-a)
            read_arg _aa "$1" "$2" || shift
            # shellcheck disable=SC2154
            arch[${#arch[@]}]="$_aa"
            ;;
        --kernelsourcedir*)
            read_arg kernel_source_dir "$1" "$2" || shift
            ksourcedir_fromcli="true"
            ;;
        --directive*)
            read_arg _da "$1" "$2" || shift
            # shellcheck disable=SC2154
            directive_array[${#directive_array[@]}]="$_da"
            ;;
        --no-depmod)
            no_depmod="true"
            ;;
        --modprobe-on-install)
            modprobe_on_install="true"
            ;;
        --debug)
            PS4='${BASH_SOURCE}@${LINENO}(${FUNCNAME[0]}): '
            export PS4
            set -x
            ;;
        -j)
            read_arg parallel_jobs "$1" "$2" || shift
            ;;
        --help|-h)
            show_usage
            exit 0
            ;;
        -*)
            error "Unknown option: $1"
            show_usage
            exit 2
            ;;
        *)
            if [[ $1 =~ $action_re ]]; then
                [[ $action ]] && die 4 "Cannot specify more than one action."
                action="$1" # Add actions to the action list
            elif [[ -f $1 && $1 = *dkms.conf ]]; then
                try_source_tree="${1%dkms.conf}./" # Flag as a source tree
            elif [[ -d $1 && -f $1/dkms.conf ]]; then
                try_source_tree="$1" # ditto
            elif [[ -f $1 ]]; then
                archive_location="$1" # It is a file, assume it is an archive.
            elif [[ ! $module ]]; then
                parse_moduleversion "$1" # Assume it is a module/version pair.
            else
                warn "I do not know how to handle $1."
            fi
            ;;
    esac
    shift
done

# Sanity checking

# Error out if binaries-only is set and source-only is set
if [[ $binaries_only && $source_only ]]; then
    die 8 "You have specified both --binaries-only and --source-only." \
        "You cannot do this."
fi

# Error if # of arches doesn't match # of kernels
if (( ${#kernelver[@]} != ${#arch[@]} && \
    ${#arch[@]} > 1 )); then
    die 1 "If more than one arch is specified on the command line, then there" \
        "must be an equal number of kernel versions also specified (1:1 relationship)."
fi

# Check that kernel version and all aren't both set simultaneously
if [[ ${#kernelver[@]} -gt 0 && $all ]]; then
    die 2 "You cannot specify a kernel version and also specify" \
        "--all on the command line."
fi

# Check that arch and all aren't both set simultaneously
if [[ ${#arch[@]} -gt 0 && $all ]]; then
    die 3 "You cannot specify an arch and also specify" \
        "--all on the command line."
fi

# Default to -j<number of CPUs>
parallel_jobs=${parallel_jobs:-$(get_num_cpus)}

# Make sure we're not passing -j0 to make; treat -j0 as just "-j"
[[ "$parallel_jobs" = 0 ]] && parallel_jobs=""

# Require explicit --kernelver argument
[[ $action =~ kernel_(preinst|postinst|prerm) ]] && have_one_kernel "$action"

setup_kernels_arches "$action"

# Since initramfs/initrd rebuild is not requested, skip it with Redhat's weak-modules
if [[ ! $NO_WEAK_MODULES ]]; then
    case "$running_distribution" in
    rhel*)
        weak_modules_add='/usr/sbin/weak-modules --no-initramfs --add-modules'
        weak_modules_remove='/usr/sbin/weak-modules --no-initramfs --remove-modules'
        ;;
    sles* | suse* | opensuse*)
        # shellcheck disable=SC2016
        weak_modules_add='/usr/lib/module-init-tools/weak-modules2 --add-kernel-modules ${kernelver}'
        # shellcheck disable=SC2016
        weak_modules_remove='/usr/lib/module-init-tools/weak-modules2 --remove-kernel-modules ${kernelver}'
        ;;
    *)
        ;;
    esac
fi

# Execute post-transaction command if set and log its output
execute_post_transaction() {
    # Blank the log file before starting
    : > "$dkms_tree/post_transaction.log"

    # Lazy source in post_transaction related configuration
    read_framework_conf "$dkms_framework_post_transaction"

    invoke_command "$post_transaction" "Executing post-transaction command" "$dkms_tree/post_transaction.log" background
}

case "$action" in
    remove | unbuild | uninstall)
        check_module_args "$action"
        module_is_broken_and_die
        module_is_added_or_die
        if [[ $action = uninstall ]]; then
            check_root
        else
            check_rw_dkms_tree
        fi
        "${action}_module"
        ret=$?
        # Execute post_transaction command if set
        if [[ $ret -eq 0 && -n "$post_transaction" ]]; then
            execute_post_transaction
        fi
        exit $ret
        ;;
    add | build | install)
        check_all_is_banned "$action" # TODO: fix/enable --all
        [[ $action != add ]] && module_is_broken_and_die
        if [[ $action = install ]]; then
            check_root
        else
            check_rw_dkms_tree
        fi
        "${action}_module"
        ret=$?
        # Execute post_transaction command if set
        if [[ $ret -eq 0 && -n "$post_transaction" && $action = install ]]; then
            execute_post_transaction
        fi
        exit $ret
        ;;
    autoinstall)
        have_one_kernel "$action"
        check_root && autoinstall
        ret=$?
        # Execute post_transaction command if set
        if [[ $ret -eq 0 && -n "$post_transaction" && ${#installed_modules[@]} -gt 0 ]]; then
            execute_post_transaction
        fi
        exit $ret
        ;;
    match)
        check_root && have_one_kernel "match" && run_match
        ;;
    mktarball)
        check_module_args mktarball
        module_is_broken_and_die
        module_is_added_or_die
        make_tarball
        ;;
    status)
        show_status
        ;;
    ldtarball) # Make sure they're root if we're using --force
        if [[ $(id -u) != 0 ]] && [[ $force = true ]]; then
            die 1 "You must be root to use this command with the --force option."
        fi
        load_tarball && add_module
        ;;
    generate_mok)
        read_framework_conf "$dkms_framework_signing_variables"
        prepare_mok
        ;;
    kernel_preinst | kernel_postinst | kernel_prerm)
        check_root && have_one_kernel "$action" && "$action"
        ;;
    *)
        error "Unknown action specified: \"$action\""
        show_usage
        ;;
esac

# vim: et:ts=4:sw=4
